Опубликован: 08.07.2011 | Уровень: для всех | Доступ: платный
Лекция 6:

Разработка Silverlight-приложений

Привязка данных

Для тестирования нашего приложения необходимо сделать привязку списка listBoxEmployees к данным. Для этого используем шаблон DataTemplate.

<ListBox Grid.Row="1" Name="listBoxEmployees" HorizontalAlignment="Center" 
                                Margin="5,2,15,9" Padding="3" Width="364" >
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=EmployeeSurname}" />
                <TextBlock Text="  " />
                <TextBlock Text="{Binding Path=EmployeeName}" />
                <TextBlock Text="  " />
                <TextBlock Text="{Binding Path=EmployeePatronymic}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

В списке будем отображать фамилию, имя и отчества сотрудника.

Фрагмент главной страницы с заполненным списком

увеличить изображение
Рис. 7.13. Фрагмент главной страницы с заполненным списком

Для привязки отображения отдельных данные из списка к детальным данным по сотруднику необходимо для контента основной Grid (LayoutRoot) выполнить привязку к текущему выделению списка listBoxEmployees:

<Grid x:Name="LayoutRoot" Background="White" Margin="10" 
VerticalAlignment="Top" Height="590" Width="779"  
DataContext="{Binding ElementName=listBoxEmployees, Path=SelectedItem, Mode=TwoWay}">

Начнем привязку детальных данных по сотруднику с текстовых полей: LastName, FirstName, SecondtName и NetName. Для элемента управления TextBox LastName привязка присоединяемого свойства Text к текущему выделению списка listBoxEmployees будет иметь следующий вид:

Text="{Binding Path= EmployeeSurname, Mode=TwoWay}"

Для объекта привязки Binding задается только привязываемое свойство ( Path=EmployeeSurname ), а элемент привязки был ранее определен в контенте основной Grid (свойство DataContext ). Свойство Mode задается значение TwoWay, что определяет двустороннюю привязку. Измененный XAML-код для интерфейсных TextBox примет следующий вид:

<TextBox Name="LastName" Height="25" Margin="5,2,2,2" 
                  Text="{Binding Path=EmployeeSurname, Mode=TwoWay}"></TextBox>
<TextBox Name="FirstName" Height="25" Margin="2" 
                 Text="{Binding Path=EmployeeName, Mode=TwoWay}"></TextBox>
<TextBox Name="SecondtName" Height="25" Margin="2" 
                Text="{Binding Path= EmployeePatronymic, Mode=TwoWay}"></TextBox>
<TextBox Name="NetName" Height="25" Margin="2" 
                            Text="{Binding Path=NetName, Mode=TwoWay}"></TextBox>

После запуска приложения и данных страница примет вид, приведенный на рис. 7.14.

Фрагмент главной страницы с детальными данными

увеличить изображение
Рис. 7.14. Фрагмент главной страницы с детальными данными

В базе данных Person атрибут EmployeeStatus является целым числом, значение которого соответствует индексу списка ComboBox comboBoxStatus приложения. Функциональность приложения определяет следующий список статуса сотрудника: не задано, активен, выходной, в отпуске, болеет, не работает. Привязка для данного элемента управления выполняется для свойства SelectedIndex.

<ComboBox Name="comboBoxStatus" Height="25" Margin="2" 
SelectedIndex = "{Binding Path=EmployeeStatus, Mode=TwoWay, BindsDirectlyToSource=True}">
        <ComboBoxItem Content="не задано" />
        <ComboBoxItem Content="активен" />
        <ComboBoxItem Content="выходной" />
        <ComboBoxItem Content="в отпуске" />
        <ComboBoxItem Content="болеет" />
        <ComboBoxItem Content="не работает" />
        <ComboBoxItem Content="помечен как удаленный" />
</ComboBox>

Уровень доступа сотрудника к ресурсам информационной системы – Access в базе данных хранится в виде символьной строки. Однако перечень уровней доступа в системе фиксирован и меняется редко. Для обеспечения работы с ограниченным списком уровней доступа пользователей введем в приложение специальный класс Access. Для этого добавим в приложение SilverlightAppPersonal класс Access, имеющий свойство AccessRole, характеризующее уровень доступа сотрудника к ресурсам сети.

public class Access
    {
        public string AccessRole { set; get; }
        public Access(string accessRole)
        {
            AccessRole = accessRole;
        }
    }

В коде класса MainPage добавим ссылку на библиотеку 
System.Collections.ObjectModel.

using System.Collections.ObjectModel;

Затем определим коллекцию уровней доступа accesses.

ObservableCollection<Access> accesses;

В коде класса MainPage добавим ссылку на библиотеку System.Collections.ObjectModel.

using System.Collections.ObjectModel;

Затем определим коллекцию уровней доступа accesses.

ObservableCollection<Access> accesses;

В конструкторе класса MainPage создадим экземпляр коллекции accesses и сформируем её из следующего списка: не задано, оператор, старший оператор, начальник смены, администратор, аналитик. Измененный код конструктора класса MainPage приведен ниже.

public MainPage()
  {
      InitializeComponent();
       accesses = new ObservableCollection<Access>();
            accesses.Add(new Access("не задано"));
            accesses.Add(new Access("оператор"));
            accesses.Add(new Access("старший оператор"));
            accesses.Add(new Access("начальник смены"));
            accesses.Add(new Access("администратор"));
            accesses.Add(new Access("аналитик"));
            comboBoxAccess.ItemsSource = accesses;
  }

После формирования коллекции её значение присваивается источнику данных выпадающего списка comboBoxAccess.

comboBoxAccess.ItemsSource = accesses;

В XAML-коде класса MainPage для выпадающего списка comboBoxAccess добавим привязку для свойства SelectedValue и зададим значение SelectedValuePath для вывода в ComboBox.

<ComboBox Height="25" Name="comboBoxAccess" Margin="5,2,2,2" 
SelectedValue="{Binding Path=Access, Mode=TwoWay}"
 SelectedValuePath="AccessRole" DisplayMemberPath="AccessRole " />

Должности сотрудников хранятся с отдельной таблице JobTitle базы данных Person. Перед загрузкой в приложение таблицы JobTitle немного усовершенствуем созданный ранее код. В методе Open_Click() всё, что связано с загрузкой в приложение данных таблицы Employee выделим в отдельный метод LoadEmployees():

private void LoadEmployees()
{
    DataServiceQuery<Employee> queryEmployee = 
                       context.Employees.AddQueryOption("$orderby", "EmployeeSurname");
    try
    {
        queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

С учетом введения метода LoadEmployees() метод Open_Click() примет следующий вид:

private void Open_Click(object sender, RoutedEventArgs e)
{
    LoadEmployees();
}

Загрузка данных таблицы JobTitle выполняется аналогично загрузке данных таблицы Employee.

Определим в классе MainPage коллекцию titles.

DataServiceCollection<JobTitle> titles;

В методе MainPage_Loaded добавим код создания экземпляра titles коллекции

DataServiceCollection< JobTitle >:
titles = new DataServiceCollection<JobTitle>();

и зарегистрируем для коллекции событие LoadCompleted с делегатом titles_LoadCompleted:

titles.LoadCompleted +=new EventHandler<LoadCompletedEventArgs>(titles_LoadCompleted);

Измененный метод MainPage_Loaded будет иметь следующий вид:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    context = 
           new PersonalEntities(new Uri("WfcDataServicePerson.svc", UriKind.Relative));
    employees = new DataServiceCollection<Employee>();
    titles = new DataServiceCollection<JobTitle>();
    employees.LoadCompleted += 
                 new EventHandler<LoadCompletedEventArgs>(employees_LoadCompleted);
    titles.LoadCompleted += 
                             new EventHandler<LoadCompletedEventArgs>(titles_LoadCompleted);
    ButtonOpen.IsEnabled = true;
}

По аналогии с методом LoadEmployees() создадим метод LoadTitles().

private void LoadTitles()
{
    DataServiceQuery<JobTitle> queryTitle = context.JobTitles;
    try
    {
        queryTitle.BeginExecute(OnTitleQueryComplete, queryTitle);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Далее спроектируем код делегата асинхронного запроса к службе данных.

private void OnTitleQueryComplete(IAsyncResult result)
{
    Dispatcher.BeginInvoke(() =>
    {
        DataServiceQuery<JobTitle> queryTitle = result.AsyncState as DataServiceQuery<JobTitle>;
        try
        {
            titles.LoadAsync(queryTitle); 
        }
        catch (DataServiceQueryException ex)
        {
            MessageBox.Show(string.Format("Ошибка запроса в БД: {0} - {1}",
            ex.Response.StatusCode.ToString(), ex.Response.Error.Message));
        }
    }
    );
}

Делегат обработки события формирования коллекции типа DataServiceCollection<JobTitle> будет иметь следующий вид:

private void titles_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        if (titles.Continuation != null)
        {
            titles.LoadNextPartialSetAsync();
        }
        else
        {
            comboBoxTitle.ItemsSource = titles;
            comboBoxTitle.UpdateLayout();
        }
    }
    else
    {
        MessageBox.Show(string.Format("An error has occured: {0}", e.Error.Message));
    }
}

Добавим в код метода Open_Click() вызов метода LoadTitles().

private void Open_Click(object sender, RoutedEventArgs e)
{
    LoadEmployees();
    LoadTitles();
}

В XAML-коде класса MainPage для выпадающего списка comboBoxTitle добавим привязку для свойства SelectedValue, SelectedValuePath и зададим значение DisplayMemberPath для вывода в ComboBox, а также обработчик события SelectionChanged:

<ComboBox Height="25" Name="comboBoxTitle" Margin="5,2,2,2" 
SelectedValue="{Binding Path=JobRoleID,  Mode=TwoWay}" 
SelectedValuePath="ID" DisplayMemberPath="Title" />

Следует пояснить, что привязка SelectedValue="{Binding Path=JobRoleID, Mode=TwoWay}" и задание свойства для привязки SelectedValuePath="ID" используют поля JobRoleID и ID таблицы Employee через контент Grid, а выводимое в ComboBox значение задается как DisplayMemberPath="Title" и при этом используется атрибут Title таблицы JobTitle коллекции titles, являющейся источником данных для comboBoxTitle.

Для календаря DatePicker необходимо построить привязку для свойства SelectedDate.

<controls:DatePicker Name="datePickerFirstDate" 
            SelectedDate="{Binding Path=FirstDate, Mode=TwoWay}" Margin="5,2,2,2"/>
<controls:DatePicker Name="datePickerLastDate" 
            SelectedDate="{Binding Path=LastDate, Mode=TwoWay}"  Margin="5,2,2,2" />

На этом привязка данных закончена и можно протестировать приложение. В результате запуска программы страница приложения должна иметь вид, приведенный на рис. 7.15.

Фрагмент главной страницы с привязанными данными

увеличить изображение
Рис. 7.15. Фрагмент главной страницы с привязанными данными
Александр Петров
Александр Петров

При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. 
Вопрос, как отследить и отключить добавление элемента в Items?

Максим Спиридонов
Максим Спиридонов

В пятой лекции на второй странице в компиляторе выскакивает ошибка в строчке :

ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;

Ошибка CS0029

Не удается неявно преобразовать тип "System.Data.Entity.DbSet<WpfApplProject.Employee>" в "System.Data.Entity.Core.Objects.ObjectQuery<WpfApplProject.Employee>".

в using прописал все как положено, здесь похоже именно с преобразованием типов проблемы

Igor Chelyadinski
Igor Chelyadinski
Беларусь, Минск, №54, 2013
Валентина Алешина
Валентина Алешина
Россия