Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get started with WPF #2535

Merged
merged 7 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
156 changes: 156 additions & 0 deletions entity-framework/core/get-started/wpf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
title: "Get Started with WPF - EF Core"
author: jeremylikness
ms.author: jeliknes
ms.date: 07/24/2020
uid: core/get-started/wpf
---
# Getting Started with WPF

This step-by-step walkthrough shows how to bind POCO types to WPF controls in a "main-detail" form. The application uses the Entity Framework APIs to populate objects with data from the database, track changes, and persist data to the database.

The model defines two types that participate in one-to-many relationship: **Category** (principal\\main) and **Product** (dependent\\detail). The WPF data-binding framework enables navigation between related objects: selecting rows in the master view causes the detail view to update with the corresponding child data.

The screen shots and code listings in this walkthrough are taken from Visual Studio 2019 16.6.5.

> [!TIP]
> You can view this article's [sample on GitHub](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/WPF).

## Pre-Requisites

* You need to have Visual Studio 2019 16.3 or later installed with the **.NET desktop workload** selected to complete this walkthrough.

For more information about installing the latest version of Visual Studio, see [Install Visual Studio](/visualstudio/install/install-visual-studio).

## Create the Application

1. Open Visual Studio
2. On the start window, choose **Create new project**.
3. Search for "WPF," choose **WPF App (.NET Core)** and then choose **Next**.
4. At the next screen, give the project a name, for example, **GetStartedWPF**, and choose **Create.**

## Install the Entity Framework NuGet packages

1. Right-click on the solution and choose **Manage NuGet Packages for Solution...**

![Manage NuGet Packages](_static/wpf-tutorial-nuget.jpg)

1. Type `entityframeworkcore.sqlite` in the search box.
1. Select the **Microsoft.EntityFrameworkCore.Sqlite** package.
1. Check the project in the right pane and click **Install**

![Sqlite Package](_static/wpf-tutorial-sqlite.jpg)

1. Repeat the steps to search for `entityframeworkcore.proxies` and install **Microsoft.EntityFrameworkCore.Proxies**.

> [!NOTE]
> When you installed the Sqlite package, it automatically pulled down the related **Microsoft.EntityFrameworkCore** base package. The **Microsoft.EntityFrameworkCore.Proxies** package provides support for "lazy-loading" data. This means when you have entities with child entities, only the parents are fetched on the initial load. The proxies detect when an attempt to access the child entities is made and automatically loads them on demand.

## Define a Model

In this walkthrough you will implement a model using "code first." This means that EF Core will create the database tables and schema based on the C# classes you define.

Right-click on the project in **Solution Explorer** and choose **Add > Class...**. Give it the name: `Product.cs` and populate it like this:

**`Product.cs`**

[!code-csharp[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/Product.cs)]

Next, add a class named `Category.cs` and populate it with the following code:

**`Category.cs`**

[!code-csharp[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/Category.cs)]

The **Products** property on the **Category** class and **Category** property on the **Product** class are navigation properties. In Entity Framework, navigation properties provide a way to navigate a relationship between two entity types.

In addition to defining entities, you need to define a class that derives from DbContext and exposes DbSet<TEntity> properties. The DbSet<TEntity> properties let the context know which types you want to include in the model.

An instance of the DbContext derived type manages the entity objects during run time, which includes populating objects with data from a database, change tracking, and persisting data to the database.

Add a new `ProductContext.cs` class to the project with the following definition:

**`ProductContext.cs`**

[!code-csharp[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/ProductContext.cs)]

* The `DbSet` informs EF Core what C# models should be mapped to the database.
* There are a variety of ways to configure EF Core data contexts. You can read about them in: [Configuring a DbContext](/ef/core/miscellaneous/configuring-dbcontext).
* This example uses the `OnConfiguring` override to specify a Sqlite data file.
* The `UseLazyLoadingProxies` call tells EF Core to implement lazy-loading, so child entities are automatically loaded when accessed from the parent.

Compile the project.

> [!TIP]
> Learn about the different was to keep your database and EF Core models in sync: [Managing Database Schemas](/ef/core/managing-schemas).

## Lazy Loading

The **Products** property on the **Category** class and **Category** property on the **Product** class are navigation properties. In Entity Framework Core, navigation properties provide a way to navigate a relationship between two entity types.

EF Core gives you an option of loading related entities from the database automatically the first time you access the navigation property. With this type of loading (called lazy loading), be aware that the first time you access each navigation property a separate query will be executed against the database if the contents are not already in the context.

When using "Plain Old C# Object" (POCO) entity types, EF Core achieves lazy loading by creating instances of derived proxy types during runtime and then overriding virtual properties in your classes to add the loading hook. To get lazy loading of related objects, you must declare navigation property getters as **public** and **virtual** (**Overridable** in Visual Basic), and your class must not be **sealed** (**NotOverridable** in Visual Basic). When using Database First, navigation properties are automatically made virtual to enable lazy loading.

## Bind Object to Controls

Add the classes that are defined in the model as data sources for this WPF application.

1. Double-click **MainWindow.xaml** in Solution Explorer to open the main form
1. Choose the **XAML** tab to edit the XAML.
1. Immediately after the opening `Window` tag, add the following sources to connect to the EF Core entities.

[!code-xaml[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml?range=1-13&highlight=9-13)]

1. This sets up source for the "parent" categories, and second source for the "detail" products.
1. Next, either drag a `DataGrid` onto the design surface and assign the `DataContext` to the `categoryViewSource`, or simply add the following markup to your XAML after the closing `Window.Resources` tag.

[!code-xaml[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml?range=14-25)]

1. Note that the `CategoryId` is set to `ReadOnly` because it is assigned by the database and cannot be changed.

## Adding a Details Grid

Now that the grid exists to display categories, the child grid can be added to show products.

**`MainWindow.xaml`**

[!code-xaml[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml?range=26-35)]

Finally, add a `Save` button and wire in the click event to `Button_Click`.

[!code-xaml[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml?range=36)]

Your design view should look like this:

![Screenshot of WPF Designer](_static/wpf-tutorial-designer.jpg)

## Add Code that Handles Data Interaction

It's time to add some event handlers to the main window.

1. In the XAML window, click on the **<Window>** element, to select the main window.
1. In the **Properties** window choose **Events** at the top right, then double-click the text box to right of the **Loaded** label.

![Main Window Properties](_static/wpf-tutorial-loaded.jpg)

This brings you to the code behind for the form, we'll now edit the code to use the `ProductContext` to perform data access. Update the code as shown below.

The code declares a long-running instance of `ProductContext`. The `ProductContext` object is used to query and save data to the database. The `Dispose()` method on the `ProductContext` instance is then called from the overridden `OnClosing` method. The code comments explain what each step does.

**`MainWindow.xaml.cs`**

[!code-csharp[](../../../samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml.cs)]

> [!NOTE]
> The code uses a call to `EnsureCreated()` to build the database on the first run. This is acceptable for demos, but in production apps you should look at [migrations](/ef/core/managing-schemas/migrations/) to manage your schema. The code also executes synchronously because it uses a local SQLite database. For production scenarios that typically involve a remote server, consider using the asynchronous versions of the `Load` and `SaveChanges` methods.

## Test the WPF Application

Compile and run the application. The database should be automatically created with a file named `products.db`. Enter a category name and hit enter, then add products to the lower grid. Click save and watch the grid refresh with the database provided ids. Highlight a row and hit **Delete** to remove the row. The entity will be deleted when you click **Save**.

![Running application](_static/wpf-tutorial-app.jpg)

## Next Steps

Learn more about [Configuring a DbContext](/ef/core/miscellaneous/configuring-dbcontext).
4 changes: 3 additions & 1 deletion entity-framework/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ additionalContent:
text: "ASP.NET Core Razor Pages web app accessing SQL Server LocalDB or SQLite with EF Core"
- url: /aspnet/core/data/ef-mvc/
text: "ASP.NET Core MVC web app accessing SQL Server with EF Core"
- url: core/get-started/wpf/
text: "WPF .NET Core app accessing SQLite with EF Core"
- url: core/get-started/xamarin/
text: "Xamarin mobile app accessing SQLite with EF Core"
# Card
Expand Down Expand Up @@ -90,7 +92,7 @@ additionalContent:
text: "Blazor Server"
- url: https://docs.microsoft.com/ef/ef6/fundamentals/databinding/wpf
text: "Windows Presentation Foundation (WPF)"
- url: https://docs.microsoft.com/ef/ef6/fundamentals/databinding/winforms
- url: core/get-started/wpf/
text: "Windows Forms (WinForms)"
- url: core/get-started/xamarin/
text: "Xamarin"
Expand Down
2 changes: 2 additions & 0 deletions entity-framework/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
href: core/get-started/install/index.md
- name: ASP.NET Core tutorial >>
href: /aspnet/core/data/ef-rp/intro
- name: WPF .NET Core Tutorial
href: core/get-started/wpf.md
- name: Xamarin tutorial
href: core/get-started/xamarin.md
- name: Fundamentals
Expand Down
25 changes: 25 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetStartedWPF", "GetStartedWPF\GetStartedWPF.csproj", "{59047498-CA21-4139-91D2-B2D87D856A85}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{59047498-CA21-4139-91D2-B2D87D856A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59047498-CA21-4139-91D2-B2D87D856A85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59047498-CA21-4139-91D2-B2D87D856A85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59047498-CA21-4139-91D2-B2D87D856A85}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8020022A-928D-4937-803A-6BBAC0432D88}
EndGlobalSection
EndGlobal
9 changes: 9 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Application x:Class="GetStartedWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GetStartedWPF"
StartupUri="MainWindow.xaml">
<Application.Resources>

</Application.Resources>
</Application>
17 changes: 17 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace GetStartedWPF
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
10 changes: 10 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Windows;

[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
15 changes: 15 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/Category.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.ObjectModel;

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

public virtual ObservableCollection<Product>
Products
{ get; private set; } =
new ObservableCollection<Product>();
}
}
14 changes: 14 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/GetStartedWPF.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<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>
<Grid DataContext="{StaticResource categoryViewSource}">
<DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
ItemsSource="{Binding}" Margin="13,13,43,229"
RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}"
Header="Category Id" Width="SizeToHeader"
IsReadOnly="True"/>
<DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}"
Header="Name" Width="*"/>
</DataGrid.Columns>
</DataGrid>
<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 x:Name="categoryIdColumn1" Binding="{Binding CategoryId}" Header="Category Id" Width="SizeToHeader"
IsReadOnly="True"/>
<DataGridTextColumn x:Name="productIdColumn" Binding="{Binding ProductId}" Header="Product Id" Width="SizeToHeader"
IsReadOnly="True"/>
<DataGridTextColumn x:Name="nameColumn1" Binding="{Binding Name}" Header="Name"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" Click="Button_Click" Height="20" Width="123"/>
</Grid>
</Window>
57 changes: 57 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 =>
FindResource(nameof(categoryViewSource))
as CollectionViewSource;

public MainWindow()
{
InitializeComponent();
}

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();
Comment on lines +45 to +47
Copy link
Contributor

@bricelam bricelam Jul 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need to call these if you implement INotifyPropertyChanged. Unless we set the fields directly by default, in which case, you should configure EF to set via properties for the best experience.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a section to explain property change notification.

}

protected override void OnClosing(CancelEventArgs e)
{
// clean up database connections
_context.Dispose();
base.OnClosing(e);
}
}
}
11 changes: 11 additions & 0 deletions samples/core/WPF/GetStartedWPF/GetStartedWPF/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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; }
}
}
Loading