hoony's web study

728x90
반응형

데이터바인딩 2번째편입니다.
요즘 조금 볼만한 msdn 내용이 없었는데 좋은 내용이 있어서 발췌해서 올립니다.
필요하신분들은 스크랩하세요 ^^

 

Shawn Wildermuth

2006 년 5 월

적용 대상:
   Microsoft Windows Presentation Foundation

요약: XAML 베이스의 데이터 바인딩을 사용하여 Microsoft Windows Presentation Foundation 프로젝트에서 데이터 조작을 실행하는 방법을 설명합니다.

시작

Windows Presentation Foundation(WPF)의 그래피컬 엔진의 위력을 활용한 샘플이 커뮤니티의 화제가 되고 있습니다. 대다수의 사용자 인터페이스 개발자의 주된 일은 엔터프라이즈 개발 환경에서 일상적인 데이터 입력 폼을 개발하는 일입니다. WPF는 개발자가 안고 있는 문제의 해결책이 될 수 있을까요? 확실한 해결책입니다.

데이터베이스 데이터에의 바인딩

Windows Presentation Foundation 데이터 바인딩 파트 1에서 기본적인 바인딩 구문을 설명 및 XAML 오브젝트에 단순한 오브젝트를 바인드 하는 방법을 다뤘습니다. 이것도 물론 중요한 요소입니다만, 대부분의 상황에서 필요한 것은 데이터 베이스에 보존된 데이터 바인딩입니다. 두 가지의 다른 시나리오로의 바인딩, 즉 데이타베이스 데이터 (예를 들어 DataSets, DataTablesDataRows) 및 커스텀 비즈니스 오브젝트가 대부분의 케이스에 적용되는 시나리오입니다.

데이터베이스 데이터의 바인딩

엔터프라이즈 개발 환경에서는 특히, 개발 작업의 중심에 있는 것은 역시 데이터베이스입니다. 구체적인 예로서 데이터베이스에서 직원 데이터를 참조할 수 있는 간단한 WPF 대화 상자의 예를 들어봅니다. 직원의 사진 등 몇가지 정보를 이 브라우저로 표시할 수 있도록 합니다. 테이블에 필요한 정보를 모두 가져와야 합니다. 다음과 같이, 데이터베이스에서 정보를 취합한 DataTable을 새롭게 작성합니다.

C#

DataTable theTable = new DataTable();
string connString = 
  ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
string query = @"SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
                 FROM Employees";

// Fill the Set with the data
using (SqlConnection conn = new SqlConnection(connString))
{
  SqlDataAdapter da = new SqlDataAdapter(query, conn);
  da.Fill(theTable);
}

Visual Basic .NET

Dim theTable As DataTable =  New DataTable() 
String connString = 
  ConfigurationManager.ConnectionStrings("Northwind").ConnectionString
String query = "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
               "  FROM Employees"
 
' Fill the Set with the data
Using conn as New SqlConnection(connString))
  Dim da As SqlDataAdapter =  New SqlDataAdapter(query,conn) 
  da.Fill(theTable)
End Using

데이터를 취득한 후, 데이터를 사용하여XAML로 바인드하는 순서대로 DataContext를 설정합니다.

C#

// Set the Data Context
DataContext = theTable;

Visual Basic .NET

' Set the Data Context
DataContext = theTable

데이터를 취득하여 윈도우에 제공하고 XAML로 데이터를 바인딩 할 수 있습니다. ComboBoxBinding은 부모의 DataContext에서 데이터를 취득하도록 지시합니다. (이 경우 WindowDataContext를 발견할 때까지, 컨트롤 트리를 거슬러 올라갑니다).

    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True"                   
              ItemsSource="{Binding}" 
              ... />

IsSynchronizedWithCurrentItem속성은 윈도우에 한해, 선택 아이템이 변화했을 때에「현재의 아이템」을 변경한다는 점이 중요합니다. 이 오브젝트를 사용하여 현재의 아이템을 변경하는 것을 WPF 엔진에 지시합니다. 이 속성이 없으면 DataContext 의 현재의 아이템은 변화하지 않고, 텍스트 박스에서는 리스트의 선두의 아이템이 계속해 현재의 아이템이라고 보여집니다.

combobox에 직원의 이름을 표시하기 위해서, DataTable에서 취득한 FirstNameLastName 를 표시하는 바인드를 ItemsTemplate 에 작성합니다.

    <DataTemplate x:Key="EmployeeListTemplate">
      <StackPanel Orientation="Horizontal">
      <TextBlock Text="{Binding Path=FirstName}" />
      <TextBlock Text=" " />
      <TextBlock Text="{Binding Path=LastName}" />
      </StackPanel>
    </DataTemplate>

이름, 직무 및 입사 연월일을 넣는 텍스트 박스를 추가합니다.

      <TextBlock Canvas.Top="5">First Name:</TextBlock>
      <TextBox Canvas.Top="5" Text="{Binding Path=FirstName}" />
      <TextBlock Canvas.Top="25">Last Name:</TextBlock>
      <TextBox Canvas.Top="25" Text="{Binding Path=LastName}" />
      <TextBlock Canvas.Top="45">Title:</TextBlock>
      <TextBox Canvas.Top="45" Text="{Binding Path=Title}" />
      <TextBlock Canvas.Top="65">Hire Date:</TextBlock>
      <TextBox Canvas.Top="65" Text="{Binding Path=HireDate}" />

사진도 필요함으로 XAML에 이미지를 추가합니다.

      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75"/>

이미지관련 유일한 문제는 사진 데이터로부터 이미지로의 자동 바인딩이 지원 되지 않는 점입니다. 이것을 간단하게 실시하려면, ComboBoxSelectionChanged 이벤트를 처리해 Image 채웁니다.

    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True"                   
              Width="200" 
              ItemsSource="{Binding}" 
              ItemTemplate="{StaticResource EmployeeListTemplate}"
              SelectionChanged="theCombo_OnSelectionChanged" />

코드에서는 DataTable 에서 이미지를 읽어와 BitmapImage 오브젝트를 작성하여 Image 태그를 씁니다. 이것은 GDI+ (System.Drawing)의 비트 맵이 아닌, WPF에서의 새로운 Bitmap 오브젝트 라는 점에 주의해 주세요.

C#

// Handler to show the image
void theCombo_OnSelectionChanged(object sender, RoutedEventArgs e)
{
  ShowPhoto();
}

// Shows the Photo for the currently selected item
void ShowPhoto()
{
  object selected = theCombo.SelectedItem;
  DataRow row = ((DataRowView)selected).Row;
  
  // Get the raw bytes of the image
  byte[] photoSource = (byte[])row["Photo"];

  // Create the bitmap object
  // NOTE: This is *not* a GDI+ Bitmap object
  BitmapImage bitmap = new BitmapImage();
  MemoryStream strm = new MemoryStream();

  // Well-known work-around to make Northwind images work
  int offset = 78;
  strm.Write(photoSource, offset, photoSource.Length - offset);

  // Read the image into the bitmap object
  bitmap.BeginInit();
  bitmap.StreamSource = strm;
  bitmap.EndInit();

  // Set the Image with the Bitmap
  theImage.Source = bitmap;
  
}

Visual Basic .NET

' Handler to show the image
Sub theCombo_OnSelectionChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)

  ShowPhoto();

End Sub

// Shows the Photo for the currently selected item
Sub ShowPhoto()

  Dim selected As Object =  theCombo.SelectedItem 
  Dim row As DataRow = (CType(selected, DataRowView)).Row 
 
  ' Get the raw bytes of the image
  Dim photoSource() As Byte = CType(row("Photo"), Byte())
 
  ' Create the bitmap object
  ' NOTE: This is *not* a GDI+ Bitmap object
  Dim bitmap As BitmapImage =  New BitmapImage() 
  Dim strm As MemoryStream =  New MemoryStream() 
 
  ' Well-known work-around to make Northwind images work
  Dim offset As Integer =  78 
  strm.Write(photoSource, offset, photoSource.Length - offset)
 
  ' Read the image into the bitmap object
  bitmap.BeginInit()
  bitmap.StreamSource = strm
  bitmap.EndInit()
 
  ' Set the Image with the Bitmap
  theImage.Source = bitmap
 
End Sub

ComboBox에서 SelectedItem 을 꺼내, DataRow 로 변환하고, 데이터를 취득할 수 있도록 합니다. 다음은 Photo 열에서 바이트 배열을 꺼냅니다. 이것은 Northwind 데이터베이스에 보존되고 있는 사진입니다. 메모리 내 스트림을 사용하고, 사진의 바이트군을 BitmapImage 오브젝트에 스트리밍 할 수 있습니다. 유일한 차이점은 잘 사용되는 회피책으로서 Northwind 의 이미지 헤더의 선두 78 바이트를 건너 뛰는 것입니다. 이 부분은 사용되지 않습니다. 스트림을 비트 맵에 읽어 들인 후, 그 스트림에 소스로서 Image 오브젝트를 할당합니다.

데이터바인딩이 쌍방향인지 어떤지를 확인할 필요가 있습니다. 현재의 정보를 표시하는 버튼을 작성합니다. 이것으로 정보가 DataRow 에 존재하는 것을 알 수 있습니다.

C#

void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
  object selected = theCombo.SelectedItem;
  DataRow row = ((DataRowView)selected).Row;

  MessageBox.Show(string.Format("{0} {1} {2} - {3:d}", 
    row["Title"], row["FirstName"], row["LastName"],  row["HireDate"]));
}

Visual Basic .NET

Sub SaveButton_OnClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

  Dim selected As Object =  theCombo.SelectedItem 
  Dim row As DataRow = (CType(selected, DataRowView)).Row 
 
  MessageBox.Show(String.Format("{0} {1} {2} - {3:d}", _
    row("Title"), row("FirstName"), row("LastName"),  row("HireDate")))

End Sub

XAML 파일 전체의 마지막 부분은 다음과 같습니다.

<Window x:Class="ExampleCS.EmployeeBrowser"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Employee Browser"
    Loaded="OnLoaded" 
    Width="300"
    Height="170" 
    WindowStartupLocation="CenterScreen"
    >
  <Window.Resources>
    <DataTemplate x:Key="EmployeeListTemplate">
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Path=FirstName}" />
        <TextBlock Text=" " />
        <TextBlock Text="{Binding Path=LastName}" />
      </StackPanel>
    </DataTemplate>
  </Window.Resources>
  <Window.Background>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <LinearGradientBrush.GradientStops>
        <GradientStop Color="DarkGray" Offset="0" />
        <GradientStop Color="White" Offset=".75" />
        <GradientStop Color="DarkGray" Offset="1" />
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Window.Background>
  <StackPanel Name="theStackPanel" 
              VerticalAlignment="Top">
    <ComboBox Name="theCombo" 
              IsSynchronizedWithCurrentItem="True" 
              Width="200" 
              ItemsSource="{Binding}" 
              ItemTemplate="{StaticResource EmployeeListTemplate}"
              SelectionChanged="theCombo_OnSelectionChanged" />
    <Canvas>
      <Canvas.Resources>
        <Style TargetType="{x:Type TextBox}">
          <Setter Property="Canvas.Left" Value="160" />
          <Setter Property="Padding" Value="0" />
          <Setter Property="Height" Value="18" />
          <Setter Property="Width" Value="120" />
        </Style>
        <Style TargetType="{x:Type TextBlock}">
          <Setter Property="Canvas.Left" Value="90" />
          <Setter Property="Padding" Value="0" />
          <Setter Property="Height" Value="18" />
          <Setter Property="FontWeight" Value="Bold" />
        </Style>
      </Canvas.Resources>
      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75"/>
      <TextBlock Canvas.Top="5">First Name:</TextBlock>
      <TextBox Canvas.Top="5" Text="{Binding Path=FirstName}" />
      <TextBlock Canvas.Top="25">Last Name:</TextBlock>
      <TextBox Canvas.Top="25" Text="{Binding Path=LastName}" />
      <TextBlock Canvas.Top="45">Title:</TextBlock>
      <TextBox Canvas.Top="45" Text="{Binding Path=Title}" />
      <TextBlock Canvas.Top="65">Hire Date:</TextBlock>
      <TextBox Canvas.Top="65" Text="{Binding Path=HireDate}" />
      <Button Canvas.Top="85" Canvas.Left="90" Width="190" 
              Name="SaveButton" Click="SaveButton_OnClick">Save</Button>
    </Canvas>
  </StackPanel>
</Window>

이 브라우저를 실행하면, 그림1과 같은 인터페이스가 표시됩니다.

그림 1. Employee Browser

비교적 간단한 예였지만, DataSet 내에서 관련하는 DataTables을 사용하는 경우는 어떨까요? 이것처럼 간단할지 시험해 봅시다.

연관 데이터 테이블의 바인딩

Employee Browser 의 기능을 확장해, 영업 담당의 직원에게는 발주를 포함하도록 합니다. 여기에는 발주 정보를 취득할 필요가 있습니다. 사용자를 바꿀 때마다 새로운 쿼리를 실행할 수도 있습니다만, 여기에서 데이터를 Employee와 함께 DataSet에 읽어와 DataRelation 을 사용하여 2 개의 정보를 관련 지어 보겠습니다.

C#

DataSet theSet = new DataSet();

string connString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
string employeeQuery = @"
  SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
  FROM Employees
";
string orderQuery = @"
  SELECT o.OrderID, EmployeeID, CompanyName, OrderDate, SUM((UnitPrice * Quantity)* (1-Discount)) as OrderTotal
  FROM Orders o
  JOIN [Order Details] od on o.OrderID = od.OrderID
   JOIN Customers c on c.CustomerID = o.CustomerID
  GROUP BY o.OrderID, o.EmployeeID, o.OrderDate, CompanyName";

// Fill the Set with the data
using (SqlConnection conn = new SqlConnection(connString))
{
  SqlDataAdapter da = new SqlDataAdapter(employeeQuery, conn);
  da.Fill(theSet, "Employees");
  da.SelectCommand.CommandText = orderQuery;
  da.Fill(theSet, "Orders");
}

// Create the relationship
DataTable empTable = theSet.Tables["Employees"];
DataTable ordTable = theSet.Tables["Orders"];
theSet.Relations.Add("Emp2Ord", 
                     empTable.Columns["EmployeeID"], 
                     ordTable.Columns["EmployeeID"], 
                     false);

// Set the Context of the Window to be the 
// DataTable we've created
DataContext = empTable;

Visual Basic .NET

Dim theSet As DataSet =  New DataSet() 
 
Dim connString As String = _
    ConfigurationManager.ConnectionStrings("Northwind").ConnectionString 
String employeeQuery = _
  "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
  "  FROM Employees"

String orderQuery = _
  "SELECT o.OrderID, EmployeeID, CompanyName, OrderDate, " + _
  "       SUM((UnitPrice * Quantity)* (1-Discount)) as OrderTotal " + 
  "FROM Orders o " +
  "JOIN (Order Details) od on o.OrderID = od.OrderID " +
  "JOIN Customers c on c.CustomerID = o.CustomerID " +
  "GROUP BY o.OrderID, o.EmployeeID, o.OrderDate, CompanyName"
 
' Fill the Set with the data
Using conn as New SqlConnection(connString)

  Dim da As SqlDataAdapter =  New SqlDataAdapter(employeeQuery,conn) 
  da.Fill(theSet, "Employees")
  da.SelectCommand.CommandText = orderQuery
  da.Fill(theSet, "Orders")

End Using
 
' Create the relationship
Dim empTable As DataTable =  theSet.Tables("Employees") 
Dim ordTable As DataTable =  theSet.Tables("Orders") 
theSet.Relations.Add("Emp2Ord", 
                     empTable.Columns("EmployeeID"), 
                     ordTable.Columns("EmployeeID"), 
                     False)

' Set the Context of the Window to be the 
' DataTable we've created
DataContext = empTable

이 코드에 의해 EmployeesOrders 의 두개의 테이블이 있는 DataSet가 작성됩니다. 이러한 테이블은 EmployeeID 에 의해 Emp2Ord라는 이름의 Relation으로 관련지을 수 있습니다. 이 경우도 EmployeeDataTable 에 바인드 하고, XAML의 데이터바인딩을 올바르게 동작시킬 수 있습니다. Windows Forms 나 ASP.NET 로의 데이터바인딩과 같이, Relation의 이름에 바인드하면 관련하는 레코드의 집합에 바인드 할 수 있습니다.

        <ListBox Name="OrderList" Width="280" Height="200"
               ItemsSource="{Binding Emp2Ord}" 
               ItemTemplate="{StaticResource OrderListTemplate}" />

이 리스트 박스에서는 관계에 의한 바인드를 지정해 있는 점을 제외하고, Employee Browser 외 부분과 같은 DataContext 를 사용하고 있습니다. 이 리스트 박스를 Relation 에 바인드 하면, Employee combobox와 같이 ItemTemplate 의 개개의 필드에 바인드 할 수 있습니다.

    <DataTemplate x:Key="OrderListTemplate">
      <StackPanel Orientation="Horizontal">
        <TextBlock VerticalAlignment="Top" Width="100" 
                   Text="{Binding Path=CompanyName}" />
        <StackPanel>
          <TextBlock Text="{Binding Path=OrderID}" />
          <TextBlock Text="{Binding Path=OrderDate}" />
          <TextBlock Text="{Binding Path=OrderTotal}" />
        </StackPanel>
      </StackPanel>
    </DataTemplate>

이 추가적인 데이터 바인딩에 의해, 선택한 사용자인 만큼 관계가 있는 발주 정보의 리스트 박스를 표시합니다. 그림 2 를 참조해 주세요.

그림 2. 개량 후의 Employee Browser

이 방법을 사용하면, 단순한 장방형의 데이터뿐만이 아니라, 보다 복잡한 데이터도 바인드 할 수 있습니다. 커스텀인 .NET 형 (또는 비즈니스 오브젝트)에 데이터나 비즈니스 논리를 보관 하고 있는 기업이 많이 있습니다. WPF 에서는 DataSets와 같이 간단하게, 이러한 오브젝트에 바인드 할 수 있습니다.

비즈니스 오브젝트에의 바인딩

.NET 발표되었을 당시, Windows Forms 및 ASP.NET을 포함하여, DataSet와 그 관련 오브젝트가 일급 시민이었습니다. 간단하게 데이터를 바인드 할 수 있고 게다가 잘 동작했습니다. 그러나, 오브젝트 모델 또는 비즈니스 오브젝트를 작성해 데이터를 보관 하는 방법을 선택하면, 오브젝트로부터 컨트롤에 데이터를 수동으로 바인드 하지 않으면 안됩니다. .NET 2.0 에서는 오브젝트가 일급 시민으로 승격되어 오브젝트에의 바인딩이 간단하게 되었습니다. WPF에서도 이것은 바뀌지 않습니다. WPF를 사용하여 DataSets 와 같이 오브젝트에의 바인딩을 실시할 수 있습니다.

비즈니스 오브젝트를 사용하여 마음에 드는 Employee Browser를 작성하려면, 우선 Employee를 넣는 클래스를 작성합니다.

C#

public class Employee
{
  // Fields
  int _employeeID;
  string _firstName;
  string _lastName;
  string _title;
  DateTime _hireDate;
  BitmapImage _photo;

  // Constructor
  public Employee(IDataRecord record)
  {
    _employeeID = (int) record["EmployeeID"];
    _firstName = (string) record["FirstName"];
    _lastName = (string)record["LastName"];
    _title = (string)record["Title"];
    _hireDate = (DateTime)record["HireDate"];
    CreatePhoto((byte[])record["Photo"]);
  }

  // BitmapImage creation
  void CreatePhoto(byte[] photoSource)
  {
    // Create the bitmap object
    // NOTE: This is *not* a GDI+ Bitmap object
    _photo = new BitmapImage();
    MemoryStream strm = new MemoryStream();

    // Well-known hack to make Northwind images work
    int offset = 78;
    strm.Write(photoSource, offset, photoSource.Length - offset);

    // Read the image into the bitmap object
    _photo.BeginInit();
    _photo.StreamSource = strm;
    _photo.EndInit();

  }
}

Visual Basic .NET

Public Class Employee

  ' Fields
  Dim _employeeID As Integer
  Dim _firstName As String
  Dim _lastName As String
  Dim _title As String
  Dim _hireDate As DateTime
  Dim _photo As BitmapImage
 
  ' Constructor
  Public  Sub New(ByVal record As IDataRecord)

    _employeeID = CType(record("EmployeeID"), Integer)
    _firstName = CType(record("FirstName"), String)
    _lastName = CType(record("LastName"), String)
    _title = CType(record("Title"), String)
    _hireDate = CType(record("HireDate"), DateTime)
    CreatePhoto(CType(record("Photo"), Byte()))

  End Sub
 
  ' BitmapImage creation
  Private  Sub CreatePhoto(ByVal photoSource() As Byte)

    ' Create the bitmap object
    ' NOTE: This is *not* a GDI+ Bitmap object
    _photo = New BitmapImage()
    Dim strm As MemoryStream =  New MemoryStream() 
 
    ' Well-known hack to make Northwind images work
    Dim offset As Integer =  78 
    strm.Write(photoSource, offset, photoSource.Length - offset)
 
    ' Read the image into the bitmap object
    _photo.BeginInit()
    _photo.StreamSource = strm
    _photo.EndInit()
 
  End Sub
End Class

이 클래스는 IDataRecord 클래스 (DataReader 에서의 하나의 결과입니다만, 이것은 나중에 설명합니다.)를 꺼내, 앞의 DataTable의 예로 사용한 것과 같은 필드를 씁니다. 여기에서는 BitmapImage 작성을 비즈니스 오브젝트로 옮기고 있는 점에 주의해 주세요. 이것은 UI 클래스에서 직원을 간단하게 사용할 수 있도록 하기 위해서입니다.

다음은 각 필드의 속성 접근자(property accessors) 가 필요합니다.

C#

// Read-Only
public int EmployeeID
{
  get { return _employeeID; }
}

public string FirstName
{
  get { return _firstName; }
  set { _firstName = value; }
}

public string LastName
{
  get { return _lastName; }
  set { _lastName = value; }
}

public string Title
{
  get { return _title; }
  set { _title = value; }
}

public DateTime HireDate
{
  get { return _hireDate; }
  set { _hireDate = value; }
}

// Read-Only
public BitmapImage Photo
{
  get { return _photo; }
}

Visual Basic .NET

' Read-Only
Public ReadOnly Property EmployeeID() As Integer
  Get 
     Return _employeeID
  End Get
End Property
 
Public Property FirstName() As String
  Get 
     Return _firstName
  End Get
  Set (ByVal Value As String) 
     _firstName = value
  End Set
End Property
 
Public Property LastName() As String
  Get 
     Return _lastName
  End Get
  Set (ByVal Value As String) 
     _lastName = value
  End Set
End Property
 
Public Property Title() As String
  Get 
     Return _title
  End Get
  Set (ByVal Value As String) 
     _title = value
  End Set
End Property
 
Public Property HireDate() As DateTime
  Get 
     Return _hireDate
  End Get
  Set (ByVal Value As DateTime) 
     _hireDate = value
  End Set
End Property
 
' Read-Only
Public ReadOnly Property Photo() As BitmapImage
  Get 
     Return _photo
  End Get
End Property

이러한 예에서는 클래스내의 필드에 읽고 쓰기 (또는 읽기 전용) 액세스만을 가능하게 합니다.

다음에는 직원을 보관 하는 콜렉션을 작성합니다.

C#

public class EmployeeList : ObservableCollection<Employee>
{
  public EmployeeList()
  {
    string connString =
           ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
    string query = @"
      SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo 
      FROM Employees
    ";

    // Fill the Set with the data
    using (SqlConnection conn = new SqlConnection(connString))
    {
      try
      {
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandText = query;

        conn.Open();
        SqlDataReader rdr = cmd.ExecuteReader();
        while (rdr.Read())
        {
          Add(new Employee(rdr));
        }
      }
      finally
      {
        if (conn.State != ConnectionState.Closed) conn.Close();
      }
    }
  }
}

Visual Basic .NET

Public Class EmployeeList
   Inherits ObservableCollection<Employee>
  Public  Sub New()
    String connString =
           ConfigurationManager.ConnectionStrings("Northwind").ConnectionString

    String query = _
      "SELECT EmployeeID, FirstName, LastName, Title, HireDate, Photo " + _
      "  FROM Employees"

    ' Fill the Set with the data
    Using conn as New SqlConnection(connString)
    
      Try
        Dim cmd As SqlCommand =  conn.CreateCommand() 
        cmd.CommandText = query
 
        conn.Open()
        Dim rdr As SqlDataReader =  cmd.ExecuteReader() 
        While rdr.Read()
          Add(New Employee(rdr))
        End While
      Finally
        If conn.State <> ConnectionState.Closed Then
            conn.Close()
        End If
      End Try
    
    End Using

  End Sub
End Class

이 콜렉션의 베이스 클래스는 ObservableCollection 클래스이며, 콜렉션에 새로운 멤버가 추가되었는지를 UI가 인식하기 위한 메커니즘을 제공합니다. 데이터 액세스를 UI 페이지로부터 콜렉션 클래스로 옮기고 있습니다. 이 클래스를 작성할 때, 데이터 베이스를 쿼리 하고, DataReader에서 새로운 직원을 콜렉션에 추가합니다.

이상으로 콜렉션 및 개개의 오브젝트의 준비가 되었으므로, 맵(자세한 설명은 이 시리즈의 파트 1를 참조)를 사용하여, 클래스를 XAML 에 가져옵니다.

<Window
    ...
    xmlns:e="Example"
    DataContext="{StaticResource EmployeeList}"
    >
  <Window.Resources>
    <e:EmployeeList x:Key="EmployeeList" />
    ...
  </Window.Resources>
  ...
</Window>

?Mapping 선언을 사용하여, 클래스를 XAML 문서에 가져오기합니다. ResourcesEmployeeList를 지정하고, 윈도우의 DataContext 에서 사용할 수 있도록 하고 있습니다. 이와 같이, XAML 파일의 나머지의 부분은 원래의 Employee Browser 와 같습니다. DataSet 의 예로 사용한 필드명과 같기 때문에입니다. 단지 하나의 변경 가능한 것은 뒤에 있는 코드로 바인드 하는 것이 아니라, XAML 문서로 BitmapImage에 바인드 합니다.

...
      <Image Name="theImage" Canvas.Top="5" Canvas.Left="5" Width="75" 
             Source="{Binding Path=Photo}"/>
...

이렇게 하여 똑같이 동작하는 Employee Browser 가 완성되었습니다 (그림 3 를 참조).

그림 3. 비즈니스 오브젝트에 근거하는 Employee Browser

형태의 맵을 사용할 뿐만 아니라 ObjectDataProvider를 사용하여 XAML 에 오브젝트를 반입할 수도 있습니다. 이 시리즈의 파트 1 에서 보여준 것처럼 키 및 형태의 이름을 지정할 뿐입니다.

    <ObjectDataProvider x:Key="EmployeeList" 
                        TypeName="Example.Data.EmployeeList, ExampleCS"/>

x:Key는 단지 바인드로 사용하는 닉네임에 지나지 않고 Typename 은 클래스명 및 어셈블리(이 경우, UI 가 존재하는 어샘블리)입니다. 같은 데이터를 읽어 들이고, XAML의 나머지의 부분은 앞부분과 같습니다.

정리

이상으로 DataSets 또는 커스텀 오브젝트를 사용하여 데이터베이스로부터 데이터를 로드하여, 그 데이터를 WPF 오브젝트에 직접 바인드 할 수 있게 되었습니다. 이제 첫 번째 WPF 데이터베이스 프로젝트를 시작할 준비가 갖춰졌습니다.
출처 : http://www.microsoft.com/korea/msdn/netframework/using/documentation/aa480226.aspx

728x90

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading