вторник, 5 января 2010 г.

Привязка к данным в своих UC

Всех с Новым 2010 годом :)
В декабре уже ушедшего 2009 года было много студентов, работы, опять же пожготовка к новому году, поэтому даже на 1 публикацию вемени не было :( А вот сейчас много выходных, сессия и все такой, поэтому буду наверстывать.

Сегодня я хотел бы поговорить о биндинге внешних данных к нашим UserControl (или CustomControl, кому как больше нравится).

Итак стоит достаточно простая задача: разработать WPF UserControl к которому будут адекватно привязываться внешние данные. Для примера рассмотрим тривиальную задачу, компонент для работы с именем, отчеством и фамилией человека. Визуально контрол будет иметь вид:


Биндинг будет осуществляться к полям LastName, FirstName и Patronimyc оответственно.
Реализовать данный UC достаточно просто:
<UserControl x:Class="ExampleBinding.UC_PersonInfo"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  MinHeight="120" MinWidth="300">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="100"></ColumnDefinition>
      <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition></RowDefinition>
      <RowDefinition></RowDefinition>
      <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Label Grid.Row="0" VerticalAlignment="Center">Фамилия:</Label>
    <Label Grid.Row="1" VerticalAlignment="Center">Имя:</Label>
    <Label Grid.Row="2" VerticalAlignment="Center">Отчество:</Label>
    <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding LastName}" VerticalAlignment="Center"></TextBox>
    <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding FirstName}" VerticalAlignment="Center"></TextBox>
    <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Patronimyc}" VerticalAlignment="Center"></TextBox>
  </Grid>
</UserControl>


* This source code was highlighted with Source Code Highlighter.

Для тестирования создадим простенькое приложение вида:

Напишем вспомогательный класс (обратите внимание, наш UC про него ничего не знает) и три обработчика:
class Person : DependencyObject
    {
      public string LastName
      {
        get { return (string)GetValue(LastNameProperty); }
        set { SetValue(LastNameProperty, value); }
      }

      // Using a DependencyProperty as the backing store for LastName. This enables animation, styling, binding, etc...
      public static readonly DependencyProperty LastNameProperty =
        DependencyProperty.Register("LastName", typeof(string), typeof(Person), new UIPropertyMetadata(""));



      public string FirstName
      {
        get { return (string)GetValue(FirstNameProperty); }
        set { SetValue(FirstNameProperty, value); }
      }

      // Using a DependencyProperty as the backing store for FirstName. This enables animation, styling, binding, etc...
      public static readonly DependencyProperty FirstNameProperty =
        DependencyProperty.Register("FirstName", typeof(string), typeof(Person), new UIPropertyMetadata(""));



      public string Patronimyc
      {
        get { return (string)GetValue(PatronimycProperty); }
        set { SetValue(PatronimycProperty, value); }
      }

      // Using a DependencyProperty as the backing store for Patronimyc. This enables animation, styling, binding, etc...
      public static readonly DependencyProperty PatronimycProperty =
        DependencyProperty.Register("Patronimyc", typeof(string), typeof(Person), new UIPropertyMetadata(""));


    }

    Person person = null;

    private void button1_Click(object sender, RoutedEventArgs e)
    {
      person = new Person { FirstName = "Иван", LastName = "Иванов", Patronimyc = "Иванович" };
      ucPersonInfo.DataContext = person;
    }

    private void button2_Click(object sender, RoutedEventArgs e)
    {
      person.LastName = "Сидоров";
    }

    private void button3_Click(object sender, RoutedEventArgs e)
    {
      MessageBox.Show(
        string.Format(
          "{0} {1} {2}",
          person.LastName,
          person.FirstName,
          person.Patronimyc
        )
        );
    }


* This source code was highlighted with Source Code Highlighter.

Убедимся чт все работало как ожидалось. По первой кнопке мы создаем обхект, и видим его значения его полей в текстбоксах. По второй изменяется состояние объекта и изменяется содержимое текстбокса. При изменении текста в полях, и нажатии на третью кнопку выдим, что изменения отобразились в объекте.
Как видите, пока ничего интересного.
Усложним задачу, пусть необходимо реализовать выбор должности на которой работает указанный человек. Для простоты в нашем UC добавим ComboBox и набор данных для отображения в нем. Изменим код нашего UC для поддержки всего этого:
public class Staff : DependencyObject
  {
    public int IdStaff
    {
      get { return (int)GetValue(IdStaffProperty); }
      set { SetValue(IdStaffProperty, value); }
    }

    // Using a DependencyProperty as the backing store for IdStaff. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IdStaffProperty =
      DependencyProperty.Register("IdStaff", typeof(int), typeof(Staff), new UIPropertyMetadata(0));



    public string StaffName
    {
      get { return (string)GetValue(StaffNameProperty); }
      set { SetValue(StaffNameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StaffName. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StaffNameProperty =
      DependencyProperty.Register("StaffName", typeof(string), typeof(Staff), new UIPropertyMetadata(""));

  }

  /// <summary>
  /// Interaction logic for UC_PersonInfo.xaml
  /// </summary>
  public partial class UC_PersonInfo : UserControl
  {
    public UC_PersonInfo()
    {
      InitializeComponent();
    }

    List<Staff> _staffList = null;

    public List<Staff> StaffList
    {
      set
      {
        _staffList = value;
        cbStaffs.DisplayMemberPath = "StaffName";    
        cbStaffs.ItemsSource = _staffList;
      }
    }



    public int IdStaff
    {
      get { return (int)GetValue(IdStaffProperty); }
      set { SetValue(IdStaffProperty, value); }
    }

    // Using a DependencyProperty as the backing store for IdStaff. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IdStaffProperty =
      DependencyProperty.Register("IdStaff", typeof(int), typeof(UC_PersonInfo), new UIPropertyMetadata(0));
  }


* This source code was highlighted with Source Code Highlighter.

Ну и соответственно поменяем код тестового приложения:
private void button1_Click(object sender, RoutedEventArgs e)
    {
      List<Staff> staffs = new List<Staff>();
      staffs.Add(new Staff { IdStaff = 1, StaffName = "Инженер" });
      staffs.Add(new Staff { IdStaff = 2, StaffName = "Начальник отдела" });
      staffs.Add(new Staff { IdStaff = 3, StaffName = "Технолог" });
      ucPersonInfo.StaffList = staffs;
      person = new Person { FirstName = "Иван", LastName = "Иванов", Patronimyc = "Иванович", IdStaff = 1};
      ucPersonInfo.DataContext = person;
    }

    private void button2_Click(object sender, RoutedEventArgs e)
    {
      person.LastName = "Сидоров";
      person.IdStaff = 2;
    }


* This source code was highlighted with Source Code Highlighter.

И вот тут и начинается самое интересное! В нашем UC есть визуальный компонент и свойство IdStaff. И теперь необходимо осуществить правильное отображение должности по иду, и присвоение правильного ида после выбора должности в комбобоксе.
Второе проще, для этого необходимо у нашего UC добавить обработчик:
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
      Binding bnd = new Binding("IdStaff");
      bnd.Mode = BindingMode.TwoWay;
      bnd.Source = e.NewValue;
      SetBinding(UC_PersonInfo.IdStaffProperty, bnd);
    }


* This source code was highlighted with Source Code Highlighter.

И для ComboBox:
private void cbStaffs_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            IdStaff = ((Staff)e.AddedItems[0]).IdStaff;
        }
А вот с возвратом все намного сложнее. Я это решение подсмотрел в DataTimePicker-е на сайте CodePlex-а.
Необходимо написать вот такой обработчик:
private static void OnIdStaffChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            int value = (int)e.NewValue;
            UC_PersonInfo uc = (UC_PersonInfo)d;
            Staff newStaff = uc._staffList.FirstOrDefault(s => s.IdStaff == value);
            if (newStaff != null)
            {
                uc.cbStaffs.SelectedItem = newStaff;
            }
        }
И подписать его для нашего свойства:
 // Using a DependencyProperty as the backing store for IdStaff.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IdStaffProperty =
            DependencyProperty.Register("IdStaff", typeof(int), typeof(UC_PersonInfo), new UIPropertyMetadata(0, OnIdStaffChanged));
Вот теперь все работает так как ожидалось, осуществляется привязка не только простых полей, но и сложных элементов в которых разорвано свойство и визуальное отображение.

P.s. Сори за последние фрагменты кода, что то сайт для оформления кода шалит...

Комментариев нет:

Отправить комментарий