Creación de una aplicación de datos sencilla con WPF y Entity Framework 6
Advertencia
Si usa Visual Studio 2022, debe usar Visual Studio 2022 versión 17.3 Preview 3 o posterior para este tutorial.
En este tutorial se muestra cómo crear una aplicación básica de "formularios sobre 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 para .NET Framework (no .NET Core ni .NET 5 o posterior). Muestra cómo realizar el enlace de datos básico con una vista de maestro y detalles, y también tiene un navegador de enlaces personalizado con botones para Mover siguiente, Mover anterior, Mover al principio, Mover al final, Actualizar y Eliminar.
Este artículo 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 sobre XAML, Entity Framework y SQL. Este ejemplo tampoco muestra la arquitectura model-View-ViewModel (MVVM), que es estándar para las aplicaciones WPF. Sin embargo, puede copiar este código en su propia aplicación MVVM con pocas modificaciones.
El código final de este tutorial se puede encontrar en GitHub en Ejemplos de tutoriales de Visual Studio: EF6.
Instalación y conexión a Northwind
En este ejemplo 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.
Si no tiene SQL Server Express LocalDB, instálelo a través del instalador de Visual Studio . En el instalador de Visual Studio , puede instalar SQL Server Express LocalDB como parte de la carga de trabajo de almacenamiento y procesamiento de datos , o como componente individual.
Instale la base de datos de ejemplo Northwind siguiendo estos pasos:
En Visual Studio, abra la ventana Explorador de objetos de SQL Server. (Explorador de objetos de SQL Server se instala como parte de la carga de trabajo Almacenamiento y procesamiento de datos en el Instalador de Visual Studio). 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.
Copie el script Northwind Transact-SQL en su Portapapeles. Este script de T-SQL crea la base de datos Northwind desde cero y la rellena con datos.
Pegue el script T-SQL en el editor de consultas y, a continuación, elija el botón Ejecutar.
Después de un breve tiempo, la consulta finaliza la ejecución y se crea la base de datos Northwind.
Agregar nuevas conexiones para Northwind.
Configuración del proyecto
En Visual Studio, cree un nuevo proyecto de aplicación WPF de C# (.NET Framework).
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.
En el Administrador de paquetes NuGet, haga clic en el vínculo Examinar. Entity Framework es probablemente el paquete principal de la lista. Haga clic en Instalar en el panel derecho y siga las indicaciones. La ventana Salida indica cuándo finaliza la instalación.
Ahora puede usar Visual Studio para crear un modelo basado en la base de datos Northwind.
Creación del modelo
Haga clic con el botón derecho en el nodo del proyecto en 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.
Llame al modelo
Northwind_model
y elija Agregar. Se abre el Asistente para Entity Data Model. Elija EF Designer en la base de datosy, a continuación, seleccione Siguiente.En la siguiente pantalla, elija la conexión LocalDB Northwind (por ejemplo, (localdb)\MSSQLLocalDB), especifique la base de datos Northwind y haga clic en Siguiente.
Si no ve una conexión, elija Nueva conexión, luego en el cuadro de diálogo Elegir origen de datos, elija Microsoft SQL Server, luego elija Continuar y en el cuadro de diálogo Propiedades de conexión, introduzca
(localdb)\MSSQLLocalDB
y en Seleccione o escriba un nombre de base de datos, elija Northwind, luego presione Aceptar.Si se le solicita, elija la versión de Entity Framework que usa.
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. Expanda el nodo dbo en la vista de árbol y elija Customers, Orders y Order Details. Deje activados los valores predeterminados y haga clic en Finalizar.
El asistente genera las clases de C# que representan el modelo de Entity Framework. Las clases son clases C# simples y antiguas y son aquellas a las que vinculamos los datos con la interfaz de usuario de WPF. 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. Puede ver todos estos archivos en el Explorador de soluciones, en el nodo Northwind_model:captura de pantalla de
La superficie del diseñador del archivo
.edmx
permite modificar algunas propiedades y relaciones en el modelo. No vamos a usar el diseñador en este tutorial.Los archivos
.tt
son de uso general y debe modificar uno de ellos para que funcione con la vinculación de datos WPF, que requiere colecciones observables. En Explorador de Soluciones, extienda el nodo Northwind_model hasta que encuentre Northwind_model.tt. (Asegúrese de que no está en el archivo .Context.tt , que está directamente debajo del archivo.edmx
).Reemplace las dos repeticiones de ICollection por ObservableCollection<T>.
Reemplace la primera aparición de HashSet<T> por ObservableCollection<T> alrededor de la línea 51. No reemplace la segunda repetición de HashSet.
Reemplace la única aparición de System.Collections.Generic (alrededor de la línea 431) por System.Collections.ObjectModel.
Presione F5 o Ctrl+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 ya está listo para enlazar este modelo a la página XAML para poder ver, navegar y modificar los datos.
Enlace de datos del modelo a la página XAML
Es posible escribir su propio código de enlace de datos, pero es mucho más fácil dejar que Visual Studio lo haga automáticamente.
En el menú principal, elija Proyecto>Agregar nuevo origen de datos para abrir el Asistente para configuración del origen de datos . Elija Objeto porque está enlazando a las clases de modelo, no a la base de datos:
Expanda el nodo del proyecto y seleccione Customer. (Los orígenes de Orders se generan automáticamente a partir de la propiedad de navegación Orders en Customer).
Haga clic en Finalizar.
Vaya a MainWindow.xaml en la vista Código. Estamos manteniendo el CÓDIGO XAML sencillo para los fines de este ejemplo. Cambie el título de MainWindow a algo más descriptivo y aumente su alto y ancho a 600 x 800 por ahora. Siempre puede cambiarlo más adelante. Ahora 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>
Abra MainWindow.xaml ahora para poder verlo en el diseñador. Esto hace que la ventana Orígenes de datos aparezca como opción en el margen de ventana de Visual Studio junto a la Caja de herramientas . Haga clic en la pestaña para abrir la ventana, presione Mayús+Alt+D o elija Ver>Otras ventanas>Orígenes de datos. Vamos a mostrar cada propiedad en la clase Customers en su propio cuadro de texto individual. En primer lugar, haga clic en la flecha del cuadro combinado Customers y elija Detalles. A continuación, arrastre el nodo a la parte central de la superficie de diseño para que el diseñador sepa que quiere que vaya en la fila central. Si lo colocas incorrectamente, puedes especificar la fila manualmente más adelante en el XAML (
Grid.Row="1"
). De forma predeterminada, los controles se colocan verticalmente en un elemento de cuadrícula, pero en este punto, puede organizarlos sin embargo, como quiera en el formulario. Por ejemplo, podría tener sentido colocar el cuadro de texto Nombre en la parte superior de la dirección. La aplicación de ejemplo de este artículo reordena los campos y los reorganiza en dos columnas.En la vista XAML, ahora puedes ver un nuevo elemento
Grid
en la fila 1 (la fila central) de la cuadrícula primaria. La cuadrícula primaria tiene un atributoDataContext
que hace referencia a un CollectionViewSource que se ha agregado al elementoWindows.Resources
. Dado ese contexto de datos, cuando el primer cuadro de texto se enlaza a Address, ese nombre se asigna a la propiedadAddress
del objetoCustomer
enCollectionViewSource
.<Grid DataContext="{StaticResource customerViewSource}">
Cuando un cliente está visible en la mitad superior de la ventana, 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 maestro y detalles funcione según lo previsto, es importante que se enlace a la propiedad Orders de la clase Customers, no al nodo Orders independiente. Arrastre la propiedad Orders de la clase Customers a la parte inferior del formulario, de modo que el diseñador la coloque en la fila 2.
Visual Studio ha generado todo el código de enlace que conecta los controles de interfaz de usuario a eventos del modelo. Todo lo que necesita hacer, para ver algunos datos, es escribir código para rellenar el modelo. En primer lugar, vaya a MainWindow.xaml.cs y agregue un miembro de datos a la clase MainWindow para el contexto de datos. Este objeto, que se ha generado automáticamente, actúa como un control que realiza un seguimiento de los cambios y eventos del modelo. También agregará miembros de datos CollectionViewSource para clientes y pedidos, y la lógica de inicialización del constructor asociado al constructor
MainWindow()
existente. La parte superior de la clase debe ser como esta: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; }
Si aún no está ahí, agregue una directiva
using
para System.Data.Entity para que el método de extensión deLoad
esté dentro del ámbito:using System.Data.Entity;
Ahora, desplácese hacia abajo y busque el controlador de eventos
Window_Loaded
. Observe que Visual Studio ha agregado un objeto CollectionViewSource. Esto representa el objeto NorthwindEntities que seleccionó al crear el modelo. Ya lo has agregado, así que no lo necesitas aquí. Vamos a reemplazar el código deWindow_Loaded
para que el método tenga el siguiente 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; }
Presione F5. Debería ver los detalles del primer cliente que se recuperó en CollectionViewSource. También debes ver sus pedidos en la rejilla de datos. El formato no es excelente, así que vamos a corregirlo. 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 proporcionaremos el CÓDIGO XAML final aquí para copiarlo en el código. También necesita algunos "formularios" (que en realidad son Cuadrículas) para permitir que el usuario agregue un nuevo cliente o pedido. Para poder agregar un cliente y un pedido nuevos, necesita un conjunto independiente de cuadros de texto que no están enlazados a datos a CollectionViewSource
. Controlará la cuadrícula que 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.
En primer lugar, agregue estos estilos al elemento Windows.Resources
en MainWindow.xaml:
<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>
A continuación, reemplace toda la cuadrícula externa por este marcador:
<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 las aplicaciones de Windows Forms, obtendrá un objeto BindingNavigator con botones para navegar por las filas de una base de datos y realizar operaciones CRUD básicas. WPF no proporciona un BindingNavigator, pero es lo suficientemente fácil crear uno. Lo hace con botones dentro de un StackPanel horizontal y asocia los botones a los comandos que están enlazados a métodos del código subyacente.
Hay cuatro partes en la lógica de comandos: (1) los comandos, (2) los enlaces, (3) los botones y (4) los controladores de comandos en el código subyacente.
Agregar comandos, enlaces y botones en XAML
En primer lugar, agregue los comandos en el archivo MainWindow.xaml dentro del elemento
Windows.Resources
:<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"/>
Un CommandBinding asigna un evento
RoutedUICommand
a un método en el código subyacente. Agregue este elementoCommandBindings
después de la etiqueta de cierreWindows.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>
Ahora, agregue el
StackPanel
con los botones de navegación, agregar, eliminar y actualizar. En primer lugar, agregue este estilo aWindows.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>
En segundo lugar, pegue este código justo después del
RowDefinitions
del elementoGrid
externo, 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 es mínimo, excepto para los métodos add y delete. La navegación se realiza mediante una llamada a métodos en la propiedad View de CollectionViewSource. DeleteOrderCommandHandler
muestra cómo realizar una eliminación en cascada en un pedido. Primero tenemos que eliminar los Detalles_del_Pedido que están asociados con él. El UpdateCommandHandler
agrega un nuevo cliente o pedido a la colección, o simplemente actualiza un cliente o pedido existente con los cambios realizados por el usuario en los cuadros de texto.
Agregue estos métodos de controlador a la clase MainWindow en MainWindow.xaml.cs. Si su CollectionViewSource para la tabla Customers tiene un nombre diferente, entonces debe ajustar el nombre en cada uno de estos 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
Presione F5 para iniciar la depuración. Debería ver los datos de clientes y pedidos rellenados en la cuadrícula y los botones de navegación deberían funcionar según lo previsto. Haga clic en Confirmar para agregar un nuevo cliente o pedido al modelo después de haber introducido los datos. Haga clic en Cancelar para salir de un formulario nuevo de pedido o cliente sin guardar los datos. Puede realizar modificaciones en los clientes y pedidos existentes directamente en los cuadros de texto y esos cambios se escriben automáticamente en el modelo.