다음을 통해 공유


방법: DataGrid 컨트롤을 사용하여 유효성 검사 구현

DataGrid 컨트롤을 사용하여 셀 수준과 행 수준 모두에서 유효성 검사를 수행할 수 있습니다. 셀 수준 유효성 검사에서는 사용자가 값을 업데이트할 때 바인딩된 데이터 개체에 대한 개별 속성의 유효성을 검사합니다. 행 수준 유효성 검사에서는 사용자가 행에 대한 변경 내용을 커밋할 때 전체 데이터 개체의 유효성을 검사합니다. 유효성 검사 오류에 대한 사용자 지정 시각적 피드백을 제공하거나, DataGrid 컨트롤에서 제공하는 기본 시각적 피드백을 사용할 수 있습니다.

다음 절차에서는 DataGrid 바인딩에 유효성 검사 규칙을 적용하고 시각적 피드백을 사용자 지정하는 방법에 대해 설명합니다.

개별 셀 값의 유효성을 검사하려면

  • 열과 함께 사용되는 바인딩에 하나 이상의 유효성 검사 규칙을 지정합니다. 이 방법은 데이터 바인딩 개요에 설명된 대로 간단한 컨트롤에서 데이터의 유효성을 검사하는 방법과 비슷합니다.

    다음 예제에서는 네 개의 열이 비즈니스 개체의 각기 다른 속성에 바인딩된 DataGrid 컨트롤을 보여 줍니다. 이 중 세 개의 열에서는 ValidatesOnExceptions 속성을 true로 설정하여 ExceptionValidationRule을 지정합니다.

    <Grid>
    
      <Grid.Resources>
        <local:Courses x:Key="courses"/>
      </Grid.Resources>
    
      <DataGrid Name="dataGrid1" FontSize="20"
        ItemsSource="{StaticResource courses}" 
        AutoGenerateColumns="False">
        <DataGrid.Columns>
          <DataGridTextColumn Header="Course Name" 
            Binding="{Binding Name, TargetNullValue=(enter a course name)}"/>
          <DataGridTextColumn Header="Course ID"
            Binding="{Binding Id, ValidatesOnExceptions=True}"/>
          <DataGridTextColumn Header="Start Date"
            Binding="{Binding StartDate, ValidatesOnExceptions=True, 
              StringFormat=d}"/>
          <DataGridTextColumn Header="End Date"
            Binding="{Binding EndDate, ValidatesOnExceptions=True,
              StringFormat=d}"/>
        </DataGrid.Columns>
      </DataGrid>
    
    </Grid>
    

    사용자가 Course ID 열에 정수가 아닌 값을 입력하는 등 올바르지 않은 값을 입력하면 셀 주위에 빨간색 테두리가 나타납니다. 다음 절차에 설명된 대로 이 기본 유효성 검사 피드백을 변경할 수 있습니다.

셀 유효성 검사 피드백을 사용자 지정하려면

  • 열의 EditingElementStyle 속성을 열의 편집 컨트롤에 적절한 스타일로 설정합니다. 편집 컨트롤은 런타임에 만들어지므로 간단한 컨트롤에서처럼 연결된 속성 Validation.ErrorTemplate을 사용할 수 없습니다.

    다음 예제에서는 유효성 검사 규칙이 설정된 세 개의 열에서 공유하는 오류 스타일을 추가하여 이전 예제를 업데이트합니다. 사용자가 올바르지 않은 값을 입력하면 이 스타일을 통해 셀 배경색이 바뀌고 도구 설명이 추가됩니다. 또한 유효성 검사 오류가 있는지 여부를 확인하기 위해 트리거를 사용합니다. 현재 셀에 대한 전용 오류 템플릿이 없으므로 이 작업은 필수적입니다.

    <DataGrid.Resources>
      <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Padding" Value="-2"/>
        <Style.Triggers>
          <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="Background" Value="Red"/>
            <Setter Property="ToolTip" 
              Value="{Binding RelativeSource={RelativeSource Self},
                Path=(Validation.Errors)[0].ErrorContent}"/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </DataGrid.Resources>
    
    <DataGrid.Columns>
      <DataGridTextColumn Header="Course Name" 
        Binding="{Binding Name, TargetNullValue=(enter a course name)}"/>
      <DataGridTextColumn Header="Course ID"
        EditingElementStyle="{StaticResource errorStyle}"
        Binding="{Binding Id, ValidatesOnExceptions=True}"/>
      <DataGridTextColumn Header="Start Date"
        EditingElementStyle="{StaticResource errorStyle}"
        Binding="{Binding StartDate, ValidatesOnExceptions=True, 
          StringFormat=d}"/>
      <DataGridTextColumn Header="End Date"
        EditingElementStyle="{StaticResource errorStyle}"
        Binding="{Binding EndDate, ValidatesOnExceptions=True,
          StringFormat=d}"/>
    </DataGrid.Columns>
    

    열에 사용된 CellStyle을 바꿔 보다 광범위한 사용자 지정을 구현할 수 있습니다.

단일 행에 있는 여러 값의 유효성을 검사하려면

  1. 바인딩된 데이터 개체의 여러 속성을 검사하는 ValidationRule 서브클래스를 구현합니다. Validate 메서드 구현에서 value 매개 변수 값을 BindingGroup 인스턴스로 캐스팅합니다. 그런 다음 Items 속성을 통해 데이터 개체에 액세스할 수 있습니다.

    다음 예제에서는 Course 개체에 대한 StartDate 속성 값이 EndDate 속성 값보다 이전인지 확인하는 프로세스를 보여 줍니다.

    Public Class CourseValidationRule
        Inherits ValidationRule
    
        Public Overrides Function Validate(ByVal value As Object, _
            ByVal cultureInfo As System.Globalization.CultureInfo) _
            As ValidationResult
    
            Dim course As Course = _
                CType(CType(value, BindingGroup).Items(0), Course)
    
            If course.StartDate > course.EndDate Then
                Return New ValidationResult(False, _
                    "Start Date must be earlier than End Date.")
            Else
                Return ValidationResult.ValidResult
            End If
    
        End Function
    
    End Class
    
    public class CourseValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value,
            System.Globalization.CultureInfo cultureInfo)
        {
            Course course = (value as BindingGroup).Items[0] as Course;
            if (course.StartDate > course.EndDate)
            {
                return new ValidationResult(false,
                    "Start Date must be earlier than End Date.");
            }
            else
            {
                return ValidationResult.ValidResult;
            }
        }
    }
    
  2. DataGrid.RowValidationRules 컬렉션에 유효성 검사 규칙을 추가합니다. RowValidationRules 속성은 해당 컨트롤에 사용된 모든 바인딩을 그룹화하는 BindingGroup 인스턴스의 ValidationRules 속성에 대한 직접 액세스를 제공합니다.

    다음 예제에서는 XAML에서 RowValidationRules 속성을 설정합니다. 바인딩된 데이터 개체가 업데이트된 후에만 유효성이 검사되도록 ValidationStep 속성은 UpdatedValue로 설정됩니다.

    <DataGrid.RowValidationRules>
      <local:CourseValidationRule ValidationStep="UpdatedValue"/>
    </DataGrid.RowValidationRules>
    

    사용자가 종료 날짜를 시작 날짜보다 이전의 날짜로 지정하면 행 머리글에 빨간색 느낌표(!)가 나타납니다. 다음 절차에 설명된 대로 이 기본 유효성 검사 피드백을 변경할 수 있습니다.

행 유효성 검사 피드백을 사용자 지정하려면

  • DataGrid.RowValidationErrorTemplate 속성을 설정합니다. 이 속성은 개별 DataGrid 컨트롤에 대한 행 유효성 검사 피드백을 사용자 지정할 수 있게 해 줍니다. 암시적 행 스타일을 사용하여 DataGridRow.ValidationErrorTemplate 속성을 설정하는 방법으로 여러 컨트롤에 영향을 줄 수도 있습니다.

    다음 예제에서는 기본 행 유효성 검사 피드백을 보다 시각적인 표시기로 바꿉니다. 사용자가 올바르지 않은 값을 입력하면 행 머리글에 흰색 느낌표가 있는 빨간색 원이 나타납니다. 이 피드백은 행 유효성 검사 오류와 셀 유효성 검사 오류 모두에 대해 발생합니다. 도구 설명에는 연결된 오류 메시지가 표시됩니다.

    <DataGrid.RowValidationErrorTemplate>
      <ControlTemplate>
        <Grid Margin="0,-2,0,-2"
          ToolTip="{Binding RelativeSource={RelativeSource
          FindAncestor, AncestorType={x:Type DataGridRow}},
          Path=(Validation.Errors)[0].ErrorContent}">
          <Ellipse StrokeThickness="0" Fill="Red" 
            Width="{TemplateBinding FontSize}" 
            Height="{TemplateBinding FontSize}" />
          <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" 
            FontWeight="Bold" Foreground="White" 
            HorizontalAlignment="Center"  />
        </Grid>
      </ControlTemplate>
    </DataGrid.RowValidationErrorTemplate>
    

예제

다음 예제에서는 셀 및 행 유효성 검사의 전체 데모를 제공합니다. Course 클래스는 IEditableObject를 구현하여 트랜잭션을 지원하는 샘플 데이터 개체를 제공합니다. DataGrid 컨트롤은 IEditableObject와 상호 작용하여 사용자가 Esc 키를 눌러 변경 내용을 되돌릴 수 있도록 합니다.

참고참고

Visual Basic을 사용하는 경우에는 MainWindow.xaml의 첫 번째 줄에서 x:Class="DataGridValidation.MainWindow"를 x:Class="MainWindow"로 바꿉니다.

유효성 검사를 테스트하려면 다음을 시도합니다.

  • Course ID 열에서 정수가 아닌 값을 입력합니다.

  • End Date 열에서 시작 날짜보다 이전인 날짜를 입력합니다.

  • Course ID, Start Date 또는 End Date에서 값을 삭제합니다.

  • 올바르지 않은 셀 값을 취소하려면 커서를 다시 셀에 놓고 Esc 키를 누릅니다.

  • 현재 셀이 편집 모드에 있을 때 전체 행의 변경 내용을 취소하려면 Esc 키를 두 번 누릅니다.

  • 유효성 검사 오류가 발생하면 마우스 포인터를 행 머리글의 표시기 위로 이동하여 연결된 오류 메시지를 확인합니다.

Imports System.Collections.ObjectModel
Imports System.ComponentModel

Public Class MainWindow

    Private Sub dataGrid1_InitializingNewItem(ByVal sender As System.Object, _
        ByVal e As System.Windows.Controls.InitializingNewItemEventArgs) _
        Handles dataGrid1.InitializingNewItem

        Dim newCourse As Course = CType(e.NewItem, Course)
        newCourse.StartDate = DateTime.Today
        newCourse.EndDate = DateTime.Today

    End Sub

End Class

Public Class Courses
    Inherits ObservableCollection(Of Course)

    Sub New()
        Me.Add(New Course With { _
            .Name = "Learning WPF", _
            .Id = 1001, _
            .StartDate = New DateTime(2010, 1, 11), _
            .EndDate = New DateTime(2010, 1, 22) _
        })
        Me.Add(New Course With { _
            .Name = "Learning Silverlight", _
            .Id = 1002, _
            .StartDate = New DateTime(2010, 1, 25), _
            .EndDate = New DateTime(2010, 2, 5) _
        })
        Me.Add(New Course With { _
            .Name = "Learning Expression Blend", _
            .Id = 1003, _
            .StartDate = New DateTime(2010, 2, 8), _
            .EndDate = New DateTime(2010, 2, 19) _
        })
        Me.Add(New Course With { _
            .Name = "Learning LINQ", _
            .Id = 1004, _
            .StartDate = New DateTime(2010, 2, 22), _
            .EndDate = New DateTime(2010, 3, 5) _
        })
    End Sub

End Class

Public Class Course
    Implements IEditableObject, INotifyPropertyChanged

    Private _name As String
    Public Property Name As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            If _name = value Then Return
            _name = value
            OnPropertyChanged("Name")
        End Set
    End Property

    Private _number As Integer
    Public Property Id As Integer
        Get
            Return _number
        End Get
        Set(ByVal value As Integer)
            If _number = value Then Return
            _number = value
            OnPropertyChanged("Id")
        End Set
    End Property

    Private _startDate As DateTime
    Public Property StartDate As DateTime
        Get
            Return _startDate
        End Get
        Set(ByVal value As DateTime)
            If _startDate = value Then Return
            _startDate = value
            OnPropertyChanged("StartDate")
        End Set
    End Property

    Private _endDate As DateTime
    Public Property EndDate As DateTime
        Get
            Return _endDate
        End Get
        Set(ByVal value As DateTime)
            If _endDate = value Then Return
            _endDate = value
            OnPropertyChanged("EndDate")
        End Set
    End Property

#Region "IEditableObject"

    Private backupCopy As Course
    Private inEdit As Boolean

    Public Sub BeginEdit() Implements IEditableObject.BeginEdit
        If inEdit Then Return
        inEdit = True
        backupCopy = CType(Me.MemberwiseClone(), Course)
    End Sub

    Public Sub CancelEdit() Implements IEditableObject.CancelEdit
        If Not inEdit Then Return
        inEdit = False
        Me.Name = backupCopy.Name
        Me.Id = backupCopy.Id
        Me.StartDate = backupCopy.StartDate
        Me.EndDate = backupCopy.EndDate
    End Sub

    Public Sub EndEdit() Implements IEditableObject.EndEdit
        If Not inEdit Then Return
        inEdit = False
        backupCopy = Nothing
    End Sub

#End Region

#Region "INotifyPropertyChanged"

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Private Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, _
           New PropertyChangedEventArgs(propertyName))
    End Sub

#End Region

End Class

Public Class CourseValidationRule
    Inherits ValidationRule

    Public Overrides Function Validate(ByVal value As Object, _
        ByVal cultureInfo As System.Globalization.CultureInfo) _
        As ValidationResult

        Dim course As Course = _
            CType(CType(value, BindingGroup).Items(0), Course)

        If course.StartDate > course.EndDate Then
            Return New ValidationResult(False, _
                "Start Date must be earlier than End Date.")
        Else
            Return ValidationResult.ValidResult
        End If

    End Function

End Class
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace DataGridValidation
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            dataGrid1.InitializingNewItem += (sender, e) =>
            {
                Course newCourse = e.NewItem as Course;
                newCourse.StartDate = newCourse.EndDate = DateTime.Today;
            };
        }
    }

    public class Courses : ObservableCollection<Course>
    {
        public Courses()
        {
            this.Add(new Course
            {
                Name = "Learning WPF",
                Id = 1001,
                StartDate = new DateTime(2010, 1, 11),
                EndDate = new DateTime(2010, 1, 22)
            });
            this.Add(new Course
            {
                Name = "Learning Silverlight",
                Id = 1002,
                StartDate = new DateTime(2010, 1, 25),
                EndDate = new DateTime(2010, 2, 5)
            });
            this.Add(new Course
            {
                Name = "Learning Expression Blend",
                Id = 1003,
                StartDate = new DateTime(2010, 2, 8),
                EndDate = new DateTime(2010, 2, 19)
            });
            this.Add(new Course
            {
                Name = "Learning LINQ",
                Id = 1004,
                StartDate = new DateTime(2010, 2, 22),
                EndDate = new DateTime(2010, 3, 5)
            });
        }
    }

    public class Course : IEditableObject, INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name == value) return;
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        private int _number;
        public int Id
        {
            get
            {
                return _number;
            }
            set
            {
                if (_number == value) return;
                _number = value;
                OnPropertyChanged("Id");
            }
        }

        private DateTime _startDate;
        public DateTime StartDate
        {
            get
            {
                return _startDate;
            }
            set
            {
                if (_startDate == value) return;
                _startDate = value;
                OnPropertyChanged("StartDate");
            }
        }

        private DateTime _endDate;
        public DateTime EndDate
        {
            get
            {
                return _endDate;
            }
            set
            {
                if (_endDate == value) return;
                _endDate = value;
                OnPropertyChanged("EndDate");
            }
        }

        #region IEditableObject

        private Course backupCopy;
        private bool inEdit;

        public void BeginEdit()
        {
            if (inEdit) return;
            inEdit = true;
            backupCopy = this.MemberwiseClone() as Course;
        }

        public void CancelEdit()
        {
            if (!inEdit) return;
            inEdit = false;
            this.Name = backupCopy.Name;
            this.Id = backupCopy.Id;
            this.StartDate = backupCopy.StartDate;
            this.EndDate = backupCopy.EndDate;
        }

        public void EndEdit()
        {
            if (!inEdit) return;
            inEdit = false;
            backupCopy = null;
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

    }

    public class CourseValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value,
            System.Globalization.CultureInfo cultureInfo)
        {
            Course course = (value as BindingGroup).Items[0] as Course;
            if (course.StartDate > course.EndDate)
            {
                return new ValidationResult(false,
                    "Start Date must be earlier than End Date.");
            }
            else
            {
                return ValidationResult.ValidResult;
            }
        }
    }

}
<Window x:Class="DataGridValidation.MainWindow"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:DataGridValidation"
  Title="DataGrid Validation Example" Height="240" Width="600">

  <Grid>

    <Grid.Resources>
      <local:Courses x:Key="courses"/>
    </Grid.Resources>

    <DataGrid Name="dataGrid1" FontSize="20" RowHeaderWidth="27"
      ItemsSource="{StaticResource courses}" 
      AutoGenerateColumns="False">

      <DataGrid.Resources>
        <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
          <Setter Property="Padding" Value="-2"/>
          <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
              <Setter Property="Background" Value="Red"/>
              <Setter Property="ToolTip" 
                Value="{Binding RelativeSource={RelativeSource Self},
                  Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </DataGrid.Resources>

      <DataGrid.Columns>
        <DataGridTextColumn Header="Course Name" 
          Binding="{Binding Name, TargetNullValue=(enter a course name)}"/>
        <DataGridTextColumn Header="Course ID"
          EditingElementStyle="{StaticResource errorStyle}"
          Binding="{Binding Id, ValidatesOnExceptions=True}"/>
        <DataGridTextColumn Header="Start Date"
          EditingElementStyle="{StaticResource errorStyle}"
          Binding="{Binding StartDate, ValidatesOnExceptions=True, 
            StringFormat=d}"/>
        <DataGridTextColumn Header="End Date"
          EditingElementStyle="{StaticResource errorStyle}"
          Binding="{Binding EndDate, ValidatesOnExceptions=True,
            StringFormat=d}"/>
      </DataGrid.Columns>

      <DataGrid.RowValidationRules>
        <local:CourseValidationRule ValidationStep="UpdatedValue"/>
      </DataGrid.RowValidationRules>

      <DataGrid.RowValidationErrorTemplate>
        <ControlTemplate>
          <Grid Margin="0,-2,0,-2"
            ToolTip="{Binding RelativeSource={RelativeSource
            FindAncestor, AncestorType={x:Type DataGridRow}},
            Path=(Validation.Errors)[0].ErrorContent}">
            <Ellipse StrokeThickness="0" Fill="Red" 
              Width="{TemplateBinding FontSize}" 
              Height="{TemplateBinding FontSize}" />
            <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" 
              FontWeight="Bold" Foreground="White" 
              HorizontalAlignment="Center"  />
          </Grid>
        </ControlTemplate>
      </DataGrid.RowValidationErrorTemplate>

    </DataGrid>

  </Grid>
</Window>

참고 항목

작업

방법: 바인딩 유효성 검사 구현

방법: 사용자 지정 개체의 유효성 검사 논리 구현

참조

DataGrid

기타 리소스

DataGrid

데이터 바인딩(WPF)