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