WPF 및 Entity Framework 6을 사용하여 간단한 데이터 애플리케이션 만들기
경고
Visual Studio 2022를 사용하는 경우 이 자습서에서는 Visual Studio 2022 버전 17.3 미리 보기 3 이상을 사용해야 합니다.
이 연습에서는 Visual Studio에서 기본 "데이터에 대한 양식" 애플리케이션을 만드는 방법을 보여 줍니다. 앱은 SQL Server LocalDB, Northwind 데이터베이스, Entity Framework 6(Entity Framework Core 아님) 및 .NET Framework용 Windows Presentation Foundation(.NET Core 또는 .NET 5 이상 아님)을 사용합니다. 마스터-세부 정보 보기로 기본 데이터 바인딩을 수행하는 방법을 보여 줍니다. 또한 다음 이동, 이전이동,시작으로 이동, 끝으로 이동, 업데이트 및 삭제대한 단추가 있는 사용자 지정 바인딩 탐색기가 있습니다.
이 문서에서는 Visual Studio에서 데이터 도구를 사용하는 데 중점을 두고 있으며 기본 기술을 자세히 설명하지 않습니다. XAML, Entity Framework 및 SQL에 대한 기본적인 지식이 있다고 가정합니다. 이 예제에서는 WPF 애플리케이션에 대한 표준인 MVVM(Model-View-ViewModel) 아키텍처도 보여주지 않습니다. 그러나 몇 가지 수정 사항으로 이 코드를 고유한 MVVM 애플리케이션에 복사할 수 있습니다.
이 자습서의 최종 코드는 GitHub의 Visual Studio 자습서 샘플 - EF6찾을 수 있습니다.
Northwind 설치 및 연결
이 예제에서는 SQL Server Express LocalDB 및 Northwind 샘플 데이터베이스를 사용합니다. 해당 제품의 ADO.NET 데이터 공급자가 Entity Framework를 지원하는 경우 다른 SQL 데이터베이스 제품에서도 작동해야 합니다.
SQL Server Express LocalDB가 없는 경우 Visual Studio 설치 관리자통해 설치합니다. Visual Studio 인스톨러에서 SQL Server Express LocalDB를 데이터 저장 및 처리 워크로드의 일부로 또는 개별 구성 요소로 설치할 수 있습니다.
다음 단계에 따라 Northwind 샘플 데이터베이스를 설치합니다.
Visual Studio에서 SQL Server 개체 탐색기 창을 엽니다. (SQL Server 개체 탐색기Visual Studio 설치 관리자Data Storage 및 처리 워크로드의 일부로 설치됩니다.) SQL Server 노드를 확장합니다. LocalDB 인스턴스를 마우스 오른쪽 단추로 클릭하고 새 쿼리 선택합니다.
쿼리 편집기 창이 열립니다.
Northwind Transact-SQL 스크립트 클립보드에 복사합니다. 이 T-SQL 스크립트는 Northwind 데이터베이스를 처음부터 만들고 데이터로 채웁니다.
T-SQL 스크립트를 쿼리 편집기에 붙여넣은 다음 실행 단추를 선택합니다.
잠시 후 쿼리 실행이 완료되고 Northwind 데이터베이스가 만들어집니다.
Northwind에 대한 새 연결 추가합니다.
프로젝트 구성
Visual Studio에서 새 C# WPF 앱(.NET Framework) 프로젝트를 만듭니다.
Entity Framework 6용 NuGet 패키지를 추가합니다. 솔루션 탐색기프로젝트 노드를 선택합니다. 주 메뉴에서 Project>NuGet 패키지 관리선택합니다.
NuGet 패키지 관리자에서, 찾아보기 링크를 클릭합니다. Entity Framework는 목록의 상위 패키지일 수 있습니다. 오른쪽 창에서 설치을 클릭하고 지시에 따릅니다. 출력 창은 설치가 완료되면 알려줍니다.
Entity Framework NuGet 패키지를 보여 주는
이제 Visual Studio를 사용하여 Northwind 데이터베이스를 기반으로 모델을 만들 수 있습니다.
모델 만들기
솔루션 탐색기 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 추가>새 항목선택합니다. 왼쪽 창의 C# 노드에서 데이터 선택하고 가운데 창에서 엔터티 데이터 모델 ADO.NET 선택합니다.
모델
Northwind_model
호출하고 추가를 선택합니다. 엔터티 데이터 모델 마법사 열립니다. 데이터베이스 에서EF 디자이너를 선택하고, 그런 다음 에서다음 을 선택합니다.다음 화면에서 LocalDB Northwind 연결(예: (localdb)\MSSQLLocalDB)을 선택하고, Northwind 데이터베이스를 지정하고, 다음클릭합니다.
연결이 표시되지 않으면 새 연결을 선택합니다. 그리고 데이터 원본 선택 대화 상자에서 Microsoft SQL Server를 선택한 다음, 계속 을 선택합니다. 연결 속성 대화 상자에서
(localdb)\MSSQLLocalDB
을 입력합니다. 그런 다음 데이터베이스 이름 선택 또는 입력에서 Northwind를 선택합니다. 마지막으로 확인을 누릅니다.메시지가 표시되면 사용 중인 Entity Framework 버전을 선택합니다.
마법사의 다음 페이지에서 Entity Framework 모델에 포함할 테이블, 저장 프로시저 및 기타 데이터베이스 개체를 선택합니다. 트리 뷰에서 dbo 노드를 확장한 다음 Customers, Orders, 그리고 주문 세부 정보를 선택합니다. 기본값을 선택한 상태로 두고 마침클릭합니다.
마법사는 Entity Framework 모델을 나타내는 C# 클래스를 생성합니다. 이 클래스들은 기본적인 C# 클래스이며, WPF 사용자 인터페이스에 데이터 바인딩되는 것입니다.
.edmx
파일은 클래스를 데이터베이스의 개체와 연결하는 관계 및 기타 메타데이터를 설명합니다..tt
파일은 모델에서 작동하고 변경 내용을 데이터베이스에 저장하는 코드를 생성하는 T4 템플릿입니다. Northwind_model 노드 아래의 솔루션 탐색기 이러한 모든 파일을 볼 수 있습니다.솔루션 탐색기 Entity Framework 모델 파일을 보여 주는
솔루션 탐색기 Entity Framework 모델 파일스크린샷
.edmx
파일의 디자이너 화면을 사용하면 모델의 일부 속성과 관계를 수정할 수 있습니다. 이 연습에서는 디자이너를 사용하지 않을 것입니다..tt
파일은 범용이며 WPF 데이터 바인딩을 사용하려면 파일 중 하나를 조정해야 합니다. 이 경우 ObservableCollections가 필요합니다. 솔루션 탐색기에서 Northwind_model 노드를 확장하여 Northwind_model.tt을 찾습니다. (.edmx
파일 바로 아래에 있는 .Context.tt 파일에 있지 않은지 확인합니다.)ICollection를 두 번 ObservableCollection<T>로 교체합니다.
51줄 주위에서 첫 번째로 나타나는 HashSet<T>를 ObservableCollection<T>로 바꾸세요. 두 번째로 등장하는 HashSet을 대체하지 마세요.
System.Collections.Generic을(를) (약 431번째 줄) System.Collections.ObjectModel으로 바꾸십시오.
F5 누르거나 Ctrl +F5눌러 프로젝트를 빌드하고 실행합니다. 애플리케이션이 처음 실행되면 모델 클래스가 데이터 원본 마법사에 표시됩니다.
이제 데이터를 보고, 탐색하고, 수정할 수 있도록 이 모델을 XAML 페이지에 연결할 준비가 되었습니다.
모델을 XAML 페이지로 데이터 바인딩
사용자 고유의 데이터 바인딩 코드를 작성할 수 있지만 Visual Studio에서 이를 수행하는 것이 훨씬 쉽습니다.
주 메뉴에서 프로젝트>새 데이터 원본 추가 선택하여 데이터 원본 구성 마법사표시합니다. 데이터베이스가 아닌 모델 클래스에 바인딩하므로 Object 선택합니다.
프로젝트의 노드를 확장하고 고객 선택합니다. (주문 원본은 고객의 주문 탐색 속성에서 자동으로 생성됩니다.)
마침클릭합니다.
코드 보기에서 MainWindow.xaml로 이동하세요. 이 예제에서는 XAML을 단순하게 유지합니다. MainWindow의 제목을 좀 더 설명적인 것으로 변경하고 현재 높이와 너비를 600 x 800으로 늘입니다. 나중에 언제든지 변경할 수 있습니다. 이제 다음 세 개의 행 정의를 메인 그리드에 추가하고, 탐색 버튼에 대한 행 하나, 고객 세부 정보에 대한 행 하나, 그리고 고객 주문을 표시하는 그리드에 대한 행 하나를 추가합니다.
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
이제 MainWindow.xaml 열어 디자이너에서 볼 수 있습니다. 이렇게 하면 데이터 원본 창이 도구 상자옆에 있는 Visual Studio 창 여백에 옵션으로 표시됩니다. 탭을 클릭하여 창을 열거나 Shift+Alt+D 누르거나 보기>기타 Windows>데이터 원본선택합니다. 고객 클래스의 각 속성을 고유한 개별 텍스트 상자에 표시하려고 합니다. 먼저 Customers 콤보 상자에서 화살표를 클릭하고 세부 정보를 선택합니다. 그런 다음 디자인 화면의 중간 부분으로 노드를 끌어 디자이너가 중간 행으로 이동하려는 것을 알 수 있도록 합니다. 잘못 배치한 경우 나중에 XAML(
Grid.Row="1"
)에서 행을 수동으로 지정할 수 있습니다. 기본적으로 컨트롤은 그리드 요소에 세로로 배치되지만 이때 폼에서 원하는 방식으로 정렬할 수 있습니다. 예를 들어 주소 위에 이름 텍스트 상자를 배치하는 것이 합리적일 수 있습니다. 이 문서의 샘플 애플리케이션은 필드를 다시 정렬하고 두 개의 열로 다시 정렬합니다.이제 XAML 보기에서 부모 Grid의 행 1(가운데 행)에서 새
Grid
요소를 볼 수 있습니다. 부모 Grid에는DataContext
특성이 있으며, 이는 CollectionViewSource이 추가된Windows.Resources
요소에 참조합니다. 데이터 컨텍스트가 지정된 경우 첫 번째 텍스트 상자가 Address바인딩할 때 해당 이름은CollectionViewSource
현재Customer
개체의Address
속성에 매핑됩니다.<Grid DataContext="{StaticResource customerViewSource}">
창의 위쪽 절반에 고객이 표시되면 아래쪽 절반에 주문이 표시되기를 원합니다. 단일 그리드 보기 컨트롤에 주문을 표시합니다. 마스터 세부 데이터 바인딩이 예상대로 작동하려면 별도의 Orders 노드가 아니라 Customers 클래스의 Orders 속성에 바인딩하는 것이 중요합니다. 디자이너가 2행에 배치하도록 Customers 클래스의 Orders 속성을 폼의 아래쪽 절반으로 끕니다.
Visual Studio는 UI 컨트롤을 모델의 이벤트에 연결하는 모든 바인딩 코드를 생성했습니다. 일부 데이터를 보려면 모델을 채우는 코드를 작성하기만 하면 됩니다. 먼저 MainWindow.xaml.cs 이동하여 데이터 컨텍스트에 대한 MainWindow 클래스에 데이터 멤버를 추가합니다. 자동으로 생성된 이 개체는 모델의 변경 내용과 이벤트를 추적하는 컨트롤처럼 작동합니다. 또한 고객 및 주문에 대한 CollectionViewSource 데이터 멤버와 연결된 생성자 초기화 논리를 기존 생성자
MainWindow()
에 추가합니다. 클래스의 맨 위는 다음과 같습니다.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; }
아직 없는 경우 System.Data.Entity에 대한
using
지시문을 추가하여Load
확장 메서드를 범위로 가져옵니다.using System.Data.Entity;
이제 아래로 스크롤하여
Window_Loaded
이벤트 처리기를 찾습니다. Visual Studio에서 CollectionViewSource 개체를 추가했습니다. 모델을 만들 때 선택한 NorthwindEntities 개체를 나타냅니다. 이미 추가했으므로 여기에 필요하지 않습니다. 이제 메서드가 다음과 같이 보이도록Window_Loaded
코드를 바꾸겠습니다.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; }
F5 누릅니다. CollectionViewSource로 검색된 첫 번째 고객에 대한 세부 정보가 표시됩니다. 또한 데이터 표에 해당 주문이 표시됩니다. 서식은 좋지 않으므로 수정해 보겠습니다. 다른 레코드를 보고 CRUD(기본 만들기, 읽기, 업데이트 및 삭제) 작업을 수행하는 방법을 만들 수도 있습니다.
페이지 디자인을 조정하고 새 고객 및 주문에 대한 그리드 추가
Visual Studio에서 생성하는 기본 정렬은 애플리케이션에 적합하지 않으므로 코드에 복사할 최종 XAML을 여기에 제공합니다. 또한 사용자가 새 고객 또는 주문을 추가할 수 있도록 몇 가지 "양식"(실제로 그리드)이 필요합니다. 새 고객과 주문을 추가하려면 CollectionViewSource
데이터 바인딩되지 않은 별도의 텍스트 상자 집합이 필요합니다. 처리기 메서드에서 Visible 속성을 설정하여 지정된 시간에 사용자에게 표시되는 그리드를 제어합니다. 마지막으로 Orders 표의 각 행에 삭제 단추를 추가하여 사용자가 개별 주문을 삭제할 수 있도록 합니다.
먼저 MainWindow.xaml Windows.Resources
요소에 다음 스타일을 추가합니다.
<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>
다음으로 전체 외부 그리드를 다음 태그로 바꿉다.
<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>
탐색, 추가, 업데이트 및 삭제하는 단추 추가
Windows Forms 애플리케이션에서는 데이터베이스의 행을 탐색하고 기본 CRUD 작업을 수행하기 위한 단추가 있는 BindingNavigator 개체를 가져옵니다. WPF는 BindingNavigator를 제공하지 않지만 쉽게 만들 수 있습니다. 수평 StackPanel 내의 버튼을 사용합니다. 그리고 당신은 이 버튼들을 코드 숨김에 있는 메서드에 바인딩된 명령어와 연관지어 실행합니다.
명령 논리에는 (1) 명령, (2) 바인딩, (3) 단추, (4) 코드 숨김의 명령 처리기 등 네 가지 부분이 있습니다.
XAML에서 명령, 바인딩 및 단추 추가
먼저
Windows.Resources
요소 내에 MainWindow.xaml 파일에 명령을 추가합니다.<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"/>
CommandBinding은
RoutedUICommand
이벤트를 코드 비하인드의 메서드에 매핑합니다.Windows.Resources
닫는 태그 뒤에 이CommandBindings
요소를 추가하세요.<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>
이제 탐색, 추가, 삭제 및 업데이트 단추가 있는
StackPanel
추가합니다. 먼저 다음 스타일을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>
둘째, 외부
Grid
요소의RowDefinitions
바로 뒤 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>
MainWindow 클래스에 명령 처리기 추가
코드 비하인드는 추가 및 삭제 메서드를 제외하고는 최소로 유지됩니다. CollectionViewSource의 View 속성에서 메서드를 호출하여 탐색을 수행합니다.
DeleteOrderCommandHandler
순서에 따라 계단식 삭제를 수행하는 방법을 보여줍니다. 우리는 먼저 연결된 Order_Details을 삭제해야 합니다.
UpdateCommandHandler
컬렉션에 새 고객 또는 주문을 추가하거나 사용자가 텍스트 상자에서 변경한 내용으로 기존 고객 또는 주문을 업데이트합니다.
MainWindow.xaml.csMainWindow 클래스에 이러한 처리기 메서드를 추가합니다. Customers 테이블에 대한 CollectionViewSource의 이름이 다른 경우 다음 각 메서드에서 이름을 조정해야 합니다.
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);
}
애플리케이션 실행
디버깅을 시작하려면 F5누릅니다. 눈금에 고객 및 주문 데이터가 채워져 있고 탐색 단추가 예상대로 작동해야 합니다. 커밋 클릭하여 데이터를 입력한 후 모델에 새 고객 또는 주문을 추가합니다. 취소를 클릭하여 데이터를 저장하지 않고 새 고객 또는 새 주문 양식을 철회합니다. 텍스트 상자에서 직접 기존 고객 및 주문을 편집할 수 있으며 이러한 변경 내용은 모델에 자동으로 기록됩니다.