XamDataGrid–원하는 편집기로 열을 동적으로 생성하고 데이터 바인딩
최근에 좋은 친구가 XamDataGrid를 사용하여 객체 모음을 기반으로 열을 동적으로 생성하는 방법을 묻는 이메일을 보냈습니다. 이것은 거의 모든 응용 프로그램이 수행해야 하는 매우 일반적인 작업처럼 보입니다. 확실히 드문 일은 아닙니다.
최근에 좋은 친구가 XamDataGrid를 사용하여 객체 모음을 기반으로 열을 동적으로 생성하는 방법을 묻는 이메일을 보냈습니다. 이것은 거의 모든 응용 프로그램이 수행해야 하는 매우 일반적인 작업처럼 보입니다. 확실히 드문 일은 아닙니다.
이 특별한 경우에는 그리드에서 열로 나타낼 개체 컬렉션을 평면화해야 합니다. 예를 들어; 런타임까지 알려지지 않은 N 수준 수의 속성 또는 특성이 있는 개체가 있을 수 있지만 그리드의 단일 행에서 개체를 편집하려고 합니다. 그리드에 바인딩 할 수 있도록 Prop1, Prop2, Prop3 등과 같은 객체에 많은 속성을 추가하고 싶지 않습니다. 얼마나 많을지 알 수 없습니다. 그리드에 열을 동적으로 추가하고 런타임에 해당 열을 자식 컬렉션의 올바른 개체에 바인딩하려고 합니다.
이 시나리오에서는 인력 배치 응용 프로그램을 빌드하고 있으며 "Period" 개체 컬렉션을 자식 속성으로 포함하는 "StaffMember" 개체가 있습니다. 내 객체는 다음과 같습니다.
public class StaffMember
{
public String Department { get; set; }
public String Name { get; set; }
public IList<Period> Periods { get; set; }
public StaffMember()
{
this.Periods = new List<Period>();
}
}
public class Period
{
public string Title { get; set; }
public int Hours { get; set; }
}
이것들은 현재 INotifyPropertyChanged를 구현하지 않는 간단한 POCO입니다. 이 데모 앱의 경우 속성 알림이 필요하지 않습니다. 프로덕션 응용 프로그램에서는 변경 알림을 위해 INotifyPropertyChanged 인터페이스를 구현해야 할 가능성이 높습니다. 다음으로 ViewModel이 필요합니다.
public class StaffMemberViewModel : INotifyPropertyChanged
{
ObservableCollection<StaffMember> _staffMembers;
public ObservableCollection<StaffMember> StaffMembers
{
get { return _staffMembers; }
set
{
_staffMembers = value;
RaisePropertyChanged("StaffMembers");
}
}
public StaffMemberViewModel()
{
PopulateStaffMembers();
}
void PopulateStaffMembers()
{
var list = new ObservableCollection<StaffMember>();
var rand = new Random();
for (Int32 i = 1; i < 4; i++)
{
var member = new StaffMember { Name = String.Format("Name {0}", i), Department = String.Format("Department {0}", i) };
for (int j = 1; j < 5; j++)
member.Periods.Add(new Period { Title = String.Format("Period {0}", j), Hours = rand.Next(0, 160) });
list.Add(member);
}
StaffMembers = list;
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(String propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
이 ViewModel은 모든 ViewModel과 마찬가지로 INotifyPropertyChanged 인터페이스를 구현합니다. 보시다시피 StaffMembers 컬렉션을 노출하는 단일 속성이 있습니다. 우리는 또한 우리를 위해 더미 데이터를 생성하는 방법을 가지고 있습니다. 다음으로 필요한 것은 View입니다.
<Window x:Class="XamDataGridDynamicColumns.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igWPF="http://schemas.infragistics.com/xaml/wpf"
xmlns:local="clr-namespace:XamDataGridDynamicColumns"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:StaffMemberViewModel />
</Window.DataContext>
<Grid>
<igWPF:XamDataGrid DataSource="{Binding Path=StaffMembers}"
FieldLayoutInitialized="xamDataGrid_FieldLayoutInitialized">
<igWPF:XamDataGrid.FieldLayoutSettings>
<igWPF:FieldLayoutSettings AutoGenerateFields="False"/>
</igWPF:XamDataGrid.FieldLayoutSettings>
<igWPF:XamDataGrid.FieldLayouts>
<igWPF:FieldLayout>
<igWPF:Field Name="Name"/>
<igWPF:Field Name="Department"/>
</igWPF:FieldLayout>
</igWPF:XamDataGrid.FieldLayouts>
</igWPF:XamDataGrid>
</Grid>
</Window>
먼저 뷰에서 두 개의 네임스페이스를 정의했습니다. 하나는 로컬 개체에 대한 것이고 다른 하나는 사용할 Infragistics 컨트롤에 대한 것입니다. 뷰의 DataContext를 StaffMemberViewModel의 인스턴스로 설정합니다. 원하는대로 할 수 있습니다. 나는 방금 XAML에서 그것을했다. 이제 XamDataGrid를 선언하고 ViewModel의 StaffMembers 컬렉션에 데이터 바인딩해야 합니다. 이제 아이디어는 직원이 항상 사용할 수 있는 이름과 부서를 가지고 있다는 것을 알고 있다는 것입니다. 이것들은 동적 열이 아니므로 FieldLayout에서 선언하여 뷰를 만들 수 있습니다. e는 AutoGenerateField = false를 설정하려고 합니다.
그렇다면 어떻게 열 생성을 시작할까요? 먼저 XamDataGrid.FieldLayoutInitialized 이벤트에 이벤트 처리기를 추가합니다. 여기에서 모든 마법이 일어나 만들고, 데이터를 바인딩하고, 편집기를 선택하고, 열을 추가합니다. ViewModel에 약간의 속성을 추가하여 필요한 모든 데이터에 액세스 할 수 있습니다.
public StaffMemberViewModel ViewModel
{
get { return this.DataContext as StaffMemberViewModel; }
}
private void xamDataGrid_FieldLayoutInitialized(object sender, Infragistics.Windows.DataPresenter.Events.FieldLayoutInitializedEventArgs e)
{
}
ViewModel 속성은 단순히 StaffMemberViewModel 형식으로 View의 DataContext를 제공합니다 (잠시 후에 이것을 사용하여 속임수를 쓸 것입니다). StaffMember의 Periods 컬렉션에 있는 Periods 수를 기반으로 열을 만들어야 합니다. 이 정보를 먼저 가져와야 하기 위해 런타임까지 얼마나 많은 기간이 있는지 알 수 없다는 것을 기억하십시오. 여기 데모 목적으로 속임수를 쓸 곳이 있습니다.
private void xamDataGrid_FieldLayoutInitialized(object sender, Infragistics.Windows.DataPresenter.Events.FieldLayoutInitializedEventArgs e)
{
//a cheat to get the number of columns to create.
var staffMember = this.ViewModel.StaffMembers.First();
for (Int32 i = 0; i < staffMember.Periods.Count; i++)
{
var field = new UnboundField
{
Name = staffMember.Periods[i].Title,
BindingMode = BindingMode.TwoWay,
BindingPath = new PropertyPath(String.Format("Periods[{0}].Hours", i))
};
e.FieldLayout.Fields.Add(field);
}
}
이 데모에서는 컬렉션의 첫 번째 StaffMember를 사용하여 만들 열 수를 결정합니다. 물론 현실 세계에서는 빌드할 열 수를 파악하기 위해 자식 컬렉션의 첫 번째 인덱스를 사용하고 싶지는 않을 것입니다. 나는 어떤 열과 얼마나 많은 열을 만들 것인지를 정의 할 일종의 정의 객체를 추천 할 것이다. 다음으로, 올바른 수의 인터레이션을 가진 루프를 만들고 새 UnboundField를 만듭니다. 세 가지 중요한 속성을 설정하려고 합니다. 첫 번째는 이름입니다. 이것은 우리에게 열 헤더를 제공 할 것입니다. 다음은 BindingMode입니다. 우리는 데이터 바인딩이 TwoWay가 되기를 원합니다. 마지막으로 BindingPath를 만듭니다. 인덱서([ ])를 사용하는 바인딩 경로를 만드는 방법에 주목하십시오. 이를 통해 우리가 평면화하는 컬렉션의 특정 인덱스에 있는 개체에 대한 바인딩을 만들 수 있습니다. 마지막으로 새로 생성된 필드를 FieldLayout에 추가하기만 하면 됩니다. 응용 프로그램을 실행하면 이것이 당신이 얻는 것입니다.

꽤 좋습니다. 객체 그래프를 성공적으로 평면화하고 기본 객체의 각 속성에 대한 각 셀에 대한 적절한 데이터 바인딩을 만들었습니다.
Choosing Your Editor
이제 당신이 무슨 생각을 하는지 압니다. 그러나 Brian, 기본 편집기는 TextBlock입니다. XamNumericEditor와 같은 다른 편집기를 사용하려면 어떻게 해야 합니까? 글쎄, 운 좋게도 그것은 매우 간단합니다. 몇 가지 코드와 약간의 XAML을 추가하기만 하면 됩니다.
private void xamDataGrid_FieldLayoutInitialized(object sender, Infragistics.Windows.DataPresenter.Events.FieldLayoutInitializedEventArgs e)
{
//a cheat to get the number of columns to create.
var staffMember = this.ViewModel.StaffMembers.First();
for (Int32 i = 0; i < staffMember.Periods.Count; i++)
{
var field = new UnboundField
{
Name = staffMember.Periods[i].Title,
BindingMode = BindingMode.TwoWay,
BindingPath = new PropertyPath(String.Format("Periods[{0}].Hours", i))
};
field.Settings.EditAsType = typeof(Int32);
field.Settings.EditorStyle = (Style)Resources["HoursFieldStyle"];
e.FieldLayout.Fields.Add(field);
}
}
이벤트 처리기에 두 줄의 코드를 추가했습니다. Field.Settings.EditAsType 속성을 설정하여 편집기에 데이터 유형을 처리하는 방법을 지시합니다. Hours 속성은 Int 형식이므로 그에 따라 속성을 설정합니다. 그렇다고 해서 XamNumericEditor가 자동으로 주어지는 것은 아닙니다. 이를 위해 EditorStyle을 제공해야 합니다. 따라서 Field.Settings.EditorStyle 속성에 "HoursFieldStyle"이라는 리소스에서 값을 가져오도록 지시하는 코드 줄을 추가합니다.
<Window.Resources>
<Style x:Key="HoursFieldStyle" TargetType="{x:Type igWPF:XamNumericEditor}">
<Setter Property="Mask" Value="###" />
</Style>
</Window.Resources>
XAML을 열고 View의 Window.Resources 속성 내에 스타일을 정의합니다. 여기서 우리가 하는 일은 XamNumericEditor를 사용하도록 지정한 다음 해당 편집기의 Mask 속성을 3자리로 설정하는 것입니다. 이제 임의의 데이터는 3자리 미만이지만 이는 예시로 사용된 것일 뿐입니다. 응용 프로그램을 실행하고 우리가 얻는 것을 볼 수 있습니다.

이제 ### 마스크와 함께 XamNumericEditor를 사용하고 있습니다. 정의한 ### 마스크와 일치하지 않는 값을 입력하려고 하면 어떻게 될까요?

맞아요! 에디터 기능은 새 값이 유효한 입력에 필요한 마스크와 일치하지 않는다는 경고를 제공해야 합니다. 꽤 멋지죠!? 그리고 그것은 너무나도 쉽습니다.
ComboBox는 어떨까요?
오케이 브라이언, 간단한 것들은 보여줬는데, 좀 더 복잡한 건 어때? XamComboEditor를 사용하고 각 셀에 대한 양식을 선택하기 위해 값으로 채우고 싶습니다. 나한테 도전하려는 거야? 일으키다. 그것. 에.
자, 먼저 해야 할 일이 있습니다. XamComboEditor에 대한 데이터 소스가 필요합니다. 이에 대한 몇 가지 접근 방식이 있으므로 하나만 선택하겠습니다. select from 값을 나타내는 객체와 데이터 소스로 사용할 클래스가 필요합니다. 이벤트는 별도의 ViewModel 일 수도 있고 현재 가지고있는 동일한 ViewModel 일 수도 있습니다. 나는 별도의 것을 사용할 것이다. 다음은 XamComboEditor에 대한 내 데이터 소스를 나타내는 클래스입니다.
public class HoursDataSource
{
public ObservableCollection<DataItem> Hours { get; set; }
public HoursDataSource()
{
PopulateHours();
}
private void PopulateHours()
{
var list = new ObservableCollection<DataItem>();
for (int i = 1; i < 160; i++)
{
list.Add(new DataItem() { Name = i.ToString(), Value = i });
}
Hours = list;
}
}
public class DataItem
{
public string Name { get; set; }
public int Value { get; set; }
}
아주 간단합니다. 별로 없습니다. 이제 XAML에 몇 가지 코드를 추가해야 합니다.
<Window.Resources>
<local:HoursDataSource x:Key="hoursDataSource" />
<igWPF:ComboBoxItemsProvider x:Key="hoursProvider" ItemsSource="{Binding Hours, Source={StaticResource hoursDataSource}}" DisplayMemberPath="Name" ValuePath="Value" />
<Style x:Key="HoursFieldStyle" TargetType="{x:Type igWPF:XamComboEditor}">
<Setter Property="ItemsProvider" Value="{StaticResource hoursProvider}" />
</Style>
<!--<Style x:Key="HoursFieldStyle" TargetType="{x:Type igWPF:XamNumericEditor}">
<Setter Property="Mask" Value="###" />
</Style>-->
</Window.Resources>
먼저 만든 첫 번째 스타일을 주석으로 처리합니다. 다음으로 HoursDataSource 개체의 인스턴스를 추가해야 합니다. 다음으로 ComboBoxItemsProvider를 만듭니다. 데이터는 ItemsSource를 HoursDataSource 인스턴스에 있는 Hours 속성에 바인딩합니다. DsiplayMemberPath=Name 및 ValuePath=Value를 설정하는 것을 잊지 마십시오. 그런 다음 이전에 사용했던 스타일을 TargetType os를 XamComboEditor로 설정하는 새 스타일로 바꿉니다. ItemsProvider 속성을 방금 만든 ComboBoxItemsProvider 리소스로 설정하는 Setter를 정의합니다. 앱을 실행하고 무슨 일이 일어나는지 봅시다.

물론 어떤 열에 어떤 편집기를 사용할지 또는 다른 열에 대해 어떤 유형을 편집해야 하는지 결정하는 논리를 포함하여 항상 이 기능을 더 기능적으로 만들 수 있습니다.
부담 없이 소스 코드를 다운로드해 주세요. 질문이 있는 경우 http://brianlagunas.com의 내 블로그, Twitter(@BrianLagunas)를 통해 저에게 연락하거나 아래에 의견을 남길 수 있습니다.