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