WPF привязка данных к шаблонам составных классов?
Я впервые пробую WPF и пытаюсь связать элементы управления с классом, созданным с использованием композиции других объектов. Например, если у меня есть класс Comp, который состоит из двух отдельных классов (обратите внимание на различные элементы, оставленные для ясности):
class One {
int _first;
int _second;
}
class Two {
string _third;
string _fourth;
}
class Comp {
int _int1;
One _part1;
Two _part2;
}
Теперь я понимаю, что могу легко связать _int1, используя "get", определенный в Comp. Но как мне привязать элементы _part1._first, _part1._second. Должен ли я выставить им "добытчиков" на уровне класса Comp? или я могу выставить их в составных классах и использовать путь привязки, который указывает на них? И как это работает с настройкой свойств?
Таким образом, это шаблон?
....
<TextBlock Name="txtBlock" Text="{Binding Path=Third}" />
....
class One {
int _first;
int _second;
}
class Two {
string _third;
string _fourth;
}
class Comp {
int _int1;
One _part1;
Two _part2;
int Int1 { get { return _int1; } set { _int1 = value; } }
int First { get { return _part1._first; } set { _part1._first = value; } }
int Second { get { return _part1._second; } set { _part1._second = value; } }
string Third { get { return _part2._third; } set { _part2._third = value; } }
string Fourth { get { return _part2.fourth; } set { _part2._fourth = value; } }
}
...
Comp oComp = new Comp();
txtBlock.DataContext = oComp;
...
Или это шаблон? (где я не уверен, что поставить на путь)
....
<TextBlock Name="txtBlock" Text="{Binding Path=_part2.Third}" />
....
class One {
int _first;
int _second;
int First { get { return _first; } set { _first = value; } }
int Second { get { return _second; } set { _second = value; } }
}
class Two {
string _third;
string _fourth;
string Third { get { return _third; } set { _third = value; } }
string Fourth { get { return _fourth; } set { _fourth = value; } }
}
class Comp {
int _int1;
One _part1;
Two _part2;
int Int1 { get { return _int1; } }
}
...
Comp oComp = new Comp();
txtBlock.DataContext = oComp;
...
Или я собираюсь заново изобрести MV-VM (которую я постепенно начинаю понимать)?
....
<TextBlock Name="txtBlock" Text="{Binding Path=Third}" />
....
class One {
int _first;
int _second;
}
class Two {
string _third;
string _fourth;
}
class Comp {
int _int1;
One _part1;
Two _part2;
}
class CompView {
Comp _comp;
CompView( Comp comp ) {
_comp = comp;
}
int Int1 { get { return _comp._int1; } set { _comp._int1 = value; } }
int First { get { return _comp._part1._first; } set { _comp._part1._first = value; } }
int Second { get { return _comp._part1._second; } set { _comp._part1._second = value; } }
string Third { get { return _comp._part2._third; } set { _comp._part2._third = value; } }
string Fourth { get { return _comp._part2.fourth; } set { _comp._part2._fourth = value; } }
}
...
Comp oComp = new Comp();
CompView oCompView = new CompView( oComp );
txtBlock.DataContext = oCompView;
...
Так как я должен делать вещи? Если это первый или третий шаблон, то кажется, что я взял все свои прекрасные (разрозненные) иерархические данные и распределил их до плоской конфигурации, чтобы связать их с элементами пользовательского интерфейса. Это должно произойти, или есть лучший способ (второй шаблон?)
редактировать
Я оставил вне вопроса, что я действительно хочу двухстороннее связывание. Таким образом, средства доступа к свойствам действительно должны иметь и устанавливать.
редактировать
Обновил мой псевдокод, чтобы показать как сеттеры, так и геттеры
редактировать
Я следовал шаблону, предоставленному Марком и Жюльеном, внедрял сеттеры и был доволен результатом. По какой-то причине я убедил себя, что установка свойства не будет следовать вплоть до конечной сущности.
2 ответа
Привязка данных работает через свойства, поэтому вы не будете использовать какие-либо переменные-члены в вашей привязке, например:
int _first
public int First { get { return _first; } }
вы будете использовать First, а не _first для привязки. Обычно я видел, как каждый класс предоставляет свои собственные свойства для привязки, и в этом случае вы можете изменить свой код на:
class One : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
int _first = 1;
int _second = 2;
public int First { get { return _first; }
set { _first = value; OnPropertyChanged("First"); } }
public int Second { get { return _second; }
set { _second = value; OnPropertyChanged("Second"); } }
}
class Two : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
string _third = "Third";
string _fourth = "Fourth";
public string Third { get { return _third; }
set { _third = value; OnPropertyChanged("Third"); } }
public string Fourth { get { return _fourth; }
set { _fourth = value; OnPropertyChanged("Fourth"); } }
}
class Comp : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
int _int1 = 100;
One _part1 = new One();
Two _part2 = new Two();
public One Part1 { get { return _part1; }
set { _part1 = value; OnPropertyChanged("Part1"); } }
public Two Part2 { get { return _part2; }
set { _part2 = value; OnPropertyChanged("Part2"); } }
public int Int1 { get { return _int1; }
set { _int1 = value; OnPropertyChanged("Int1"); } }
}
Обратите внимание, что я сделал свойства общедоступными, оставив поля по умолчанию частными. Если вы назначите DataContext родительского элемента управления экземпляру Comp:
Comp comp = new Comp();
stack.DataContext = comp;
Затем вы можете привязать к частям в xaml следующим образом:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp"
Title="Window1" Height="300" Width="300">
<StackPanel Name="stack">
<TextBlock Text="{Binding Int1}"/>
<TextBlock Text="{Binding Part1.First}"/>
<TextBlock Text="{Binding Part1.Second}"/>
<TextBlock Text="{Binding Part2.Third}"/>
<TextBlock Text="{Binding Part2.Fourth}"/>
</StackPanel>
</Window>
здесь вы увидите, что StackPanel получает Comp в качестве DataContext (и, следовательно, все его дочерние элементы также имеют этот DataContext, если не указан другой), и текст привязан к его классам-членам.
РЕДАКТИРОВАТЬ: Я также добавил в сеттеры, а также реализовал INotifyPropertyChanged, который является важным компонентом для привязки данных. Благодаря этому вы сможете привязать свои данные к нескольким элементам управления и просматривать обновления данных во всех элементах управления при их обновлении. Вам нужно будет добавить: using System.ComponentModel;
Я думаю, что вы всегда должны привязываться к свойству, поэтому ваши классы должны быть:
class One {
int _first;
int _second;
int First { get { return _first; } }
int Second { get { return _second; } }
}
class Two {
string _third;
string _fourth;
string Third { get { return _third; } }
string Fourth { get { return fourth; } }
}
class Comp {
int _int1;
One _part1;
Two _part2;
One Part1 { get { return _part1; } }
Two Part2 { get { return _part2; } }
}
Затем вы сможете связать все, что захотите:
....
<TextBlock Name="txtBlock" Text="{Binding Path=Part2.Third}" />
....