Цель лекции: рассмотреть основы привязки данных, узнать, как извлечь информацию из одного элемента и отобразить в другом, не написав ни единой строки кода. Научиться реализовывать интерфейсы INotifyPropertyChanged, INotifyCollectionChanged.
Платформа Windows Presentation Foundation отличается от многих других каркасов для создания пользовательских интерфейсов в части обработки данных. Хотя WPF/Silverlight и позволяет смешивать данные и пользовательский интерфейс, модели,
Как правило, приложение создается для отображения или создания тех или иных данных. Что бы ни представляли собой данные – документ, базу данных или чертеж, главная задача приложения состоит в том, чтобы их отобразить, создать и отредактировать. Способов представления данных столько же, сколько приложений. Уже с момента возникновения на платформе .NET существовала стандартная модель, которая существенно изменила подходы к обработке данных.
Модель данных описывает контракт между источником и потребителем данных. Исторически сложилось так, что каждый каркас нес с собой новую модель данных: В Visual Basic это сначала была DAO (
В .NET имеется некая объектная модель, содержащаяся классы, интерфейсы, структуры, перечисления, делегаты и т.д. Но сверх того включена очень простая модель данных. Списки в .NET представляются интерфейсами из пространства имен System.Collections:
В .NET также имеется и несколько конкретных способов работы с данными, например: ADO.NET (пространство имен System.Data), XML (пространство имен System.Xml), контракт о данных (пространство имен System.Runtime.Serialization) и разметка (пространство имен System.Windows.Markup). И это далеко не все. Самое важное, что все они построены поверх базовой модели данных .NET.
Поскольку все операции с данными в WPF/Silverlight базируются на фундаменте модели данных .NET, то элементы управления WPF могут получать данные от любого объекта CLR:
ListBox listBox1 = new ListBox();
listBox1.ItemsSource = new string[] { "Hello", "World" };
Из всех нововведений, появившихся в WPF, для программиста, пожалуй, самым важным является полная интеграция связывания в систему. Шаблоны элементов, ресурсы и даже базовая "модель содержимого", лежащая в основе таких элементов управления, как Button и ListBox, – все может быть привязано к данным. Идея привязки свойств к источникам данным, отслеживания зависимостей и автоматического обновления экрана пронизывает все части WPF/Silverlight.
У связывания в WPF много названий. Например, для привязки к шаблонам в разметке применяется обозначение TemplateBinding вместо стандартной нотации Binding, используемой для привязки к данным. Привязка к шаблону работает лишь в контексте некоего шаблона, и при этом допускается привязывать лишь свойства шаблонного элемента управления. Это ограничение обеспечивает очень высокую эффективность привязки, что абсолютно необходимо, так как в WPF/Silverlight очень многие элементы управления привязываются к шаблонам. Привязка позволяет синхронизировать два представления данных. В WPF это означает, что некие данные (то есть свойство источника данных) привязываются к свойству элемента пользовательского интерфейса. Привязка прекрасно годится для поддержания синхронизации между двумя объектами, но, если типы данных этих объектов согласуются не идеально, начинают возникать проблемы.
Чтобы идея привязки действительно проникла во все части каркаса, необходимы способы преобразования данных.
В любом каркасе с повсеместной привязкой доступ к произвольным данным возможен лишь тогда, когда эти данные могут быть преобразованы в любом месте, где привязка поддерживается (кроме привязки ресурсов).
WPF поддерживает два основных вида преобразований: конвертацию значений и шаблоны данных. Конвертеры значений просто преобразовывают данные из одного формата в другой (например, строку "Red" в Brushes.Red). Это весьма мощный механизм, поскольку конвертер может осуществлять двустороннее преобразование. Шаблоны данных, с другой стороны, позволяют "на лету" создавать элементы управления для представления данных. В модели шаблонов данных элементы управления могут с помощью привязок обращаться к данным, то есть читать и потенциально даже записывать данные обратно в источник.
С помощью преобразований можно получить очень выразительные представления данных. Рассмотрим работу с данными в WPF с одного из наиболее применяемых источников – ресурсов. В первой лекции мы уже столкнулись с данным вопросом, теперь рассмотрим его более подробно
Самое первое место, где программист сталкивается с необходимостью отделить отображение от самих данных – это ресурсы. У каждого элемента есть свойство Resources, представляющее собой обычный словарь, который позволяет искать значение по ключу. Эта простая техника применяется в механизмах поддержки тем, стилизации и привязки к данным.
В программе на языке C# можно определять переменные для последующего использования. Часто это делается ради удобства чтения кода, а иногда в целях обобществления:
public class Window1 : Window
{
public Window1()
{
Title = "Resources";
Brush toShare = new SolidColorBrush(Colors.Yellow);
Button b = new Button();
b.Content = "My Button";
b.Background = toShare;
Content = b;
}
}
В разметке эта проблема не так проста. Модель грамматического разбора XAML требует, чтобы все создаваемые объекты были свойствами чего-либо. Захоти мы получить кисть, общую для двух кнопок, пришлось бы создавать какой-то вид привязки одного элемента к другому, а это, очевидно, куда сложнее, чем просто завести переменную. Поэтому при разработке WPF было решено ввести единое свойство Resources, в котором можно хранить список именованных объектов. Эти объекты стали бы тогда доступны любому дочернему элементу. В следующем фрагменте кисть определяется в свойстве Resources окна, а затем используется в кнопке:
<Window
Text=’Resources’
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Window.Resources>
<SolidColorBrush x:Key=’toShare’>Yellow</SolidColorBrush>
</Window.Resources>
<Button Background=’{StaticResource toShare}’>
My Button
</Button>
</Window>
Путь поиска ресурса несколько сложнее, чем просто проход вверх по иерархии. Просматривается также объект приложения, системная тема и тема по умолчанию для типов. Порядок просмотра таков:
Ранее мы говорили, что у класса типа Application есть свойство Resources, позволяющее определить ресурсы, глобальные для всего приложения. Рассмотрим на примере:
<Application x:Class=’EssentialWPF.MyApp’
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Application.Resources>
<SolidColorBrush x:Key=’toShare’>Purple</SolidColorBrush>
</Application.Resources>
</Application>
Эта техника позволяет создавать ресурсы, общие для всех страниц, окон и элементов управления. На любом уровне приложения можно переопределить глобальное значение ресурса. В общем случае рекомендуется определять ресурс на самом нижнем возможном уровне. Если некий ресурс используется только в одной панели, то для этой панели его и надо определить. Если же ресурс используется в нескольких окнах, то определить его следует на уровне приложения. Определяя ресурс, важно помнить, что использовать его можно в разных местах. Поскольку каждый элемент WPF может присутствовать только в одном месте дерева отображения, мы не можем надежно использовать элемент в качестве ресурса:
<Window
x:Class=’EssentialWPF.Resources’
Title=’Resources’
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Window.Resources>
<TextBox x:Key=’sharedTextBox’ />
</Window.Resources>
<Button Content=’{StaticResource sharedTextBox}’/>
</Window>
Эта разметка будет работать только, если на элемент sharedTextBox есть только одна ссылка. Попытайся мы воспользоваться этим ресурсом еще раз, приложение завершится с ошибкой:
<Window
x:Class=’EssentialWPF.Resources’
Title=’Resources’
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Window.Resources>
<TextBox x:Key=’sharedTextBox’ />
</Window.Resources>
<StackPanel>
<!— это ошибка! —>
<Button Content=’{StaticResource sharedTextBox}’/>
<Button Content=’{StaticResource sharedTextBox}’/>
</StackPanel>
</Window>
Когда ресурс требуется использовать более одного раза, необходимо прибегнуть к классу FrameworkElementFactory. Для элементов, принадлежащих шаблонам, мы создаем фабрику, а не сами элементы. Большинству визуальных объектов (кистей, перьев, сеток и т.д.) фабрика не нужна, так как многократное использование обеспечивается наследованием классу Freezable.
Почему в лекции о привязке к данным мы говорим о ресурсах? Дело в том, что, используя ссылки на статические ресурсы, мы по существу выполняем присваивание переменной, как в приведенном выше фрагменте на C#. Когда эта переменная используется, никакой связи с исходной переменной уже нет. Рассмотрим следующий код:
Brush someBrush = Brushes.Red; Button button1 = new Button(); button1.Background = someBrush; someBrush = Brushes.Yellow; // button1.Background здесь будет красным
При работе с ресурсами можно либо выполнить аналогичное статическое связывание в форме присваивания, либо организовать динамическое связывание:
<Window
Title=’Resources’
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Window.Resources>
<SolidColorBrush x:Key=’toShare’>Yellow</SolidColorBrush>
</Window.Resources>
<Button Background=’{DynamicResource toShare}’>
My Button
</Button>
</Window>
Поскольку на этот раз мы воспользовались динамическим связыванием, то можем изменить цвет кнопки, присвоив новое значение свойству Resources окна:
<!— window1.xaml —>
...
<Button Background=’{DynamicResource toShare}’ Click=’Clicked’>
My Button
</Button>
...
// window1.xaml.cs
...
void Clicked(object sender, RoutedEventArgs e)
{
Brush newBrush = new SolidColorBrush(Colors.Blue);
this.Resources["toShare"] = newBrush;
}
...
Это очень полезный механизм. В сочетании с иерархической областью видимости ресурсов он позволяет обновить сразу все окна или страницы приложения.
Чтобы выполнить динамическое связывание ресурса программно, нам понадобится метод FrameworkElement.SetResourceReference:
button1.SetResourceReference(Button.BackgroundProperty,"toShare");
Конечно, такие динамические ссылки не обходятся без накладных расходов, поскольку система должна отслеживать изменения ресурса.
При изменении любого ресурса обновляется все дерево. Поэтому статические и динамические ссылки на ресурсы можно использовать во многих местах, стоимость операции от количества ссылок не зависит. А вот часто изменять ресурсы для обновления пользовательского интерфейса не стоит. Зато и тревожиться по поводу большого числа ссылок на ресурсы не надо.
Ресурсы – это особая форма привязки к данным, оптимизированная в расчете на большое число привязок, которые редко обновляются. В общем случае, механизм привязки к данным оптимизирован в предположении умеренного числа привязок (в том числе и двусторонних) с высокой частотой обновления. Этот более общий вид привязки получил и более простое название; в WPF он называется просто связыванием или привязкой.
Связывание – это просто способ синхронизации двух элементов данных. Элемент данных (data point) – абстрактное понятие, выражающее идею "точки" в пространстве данных. Описать элемент данных можно разными способами; чаще всего он представляется источником данных и запросом. Например, элемент данных "свойство" состоит из объекта и имени свойства. Имя свойства определяет само свойство, а объект служит источником данных для этого свойства.
В WPF элемент данных представлен классом Binding. Для конструирования привязки мы указываем источник (данных) и путь (запрос). В следующем примере создается элемент данных, ссылающийся на свойство Text объекта TextBox:
Binding bind = new Binding();
bind.Source = textBox1;
bind.Path = new PropertyPath("Text");
Нужен еще второй элемент данных, который будет синхронизован с первым. Поскольку связывание в WPF ограничивается только деревом элементов, то для определения какого-либо элемента данных нужно вызвать метод SetBinding. Этот метод вызывается для источника данных, а данные привязываются к запросу (в данном примере к свойству ContentControl.ContentProperty):
contentControl1.SetBinding(ContentControl.Content, bind);
В этом примере свойство Text объекта textBox1 связывается со свойством Content объекта contentControl1. То же самое можно было бы выразить на XAML
<Window ... Title=’ExampleBind’>
<StackPanel>
<TextBox x:Name=’textBox1’ />
<ContentControl
Margin=’5’ x:Name=’contentControl1’
Content=’{Binding ElementName=textBox1,Path=Text}’ />
</StackPanel>
</Window>
Когда привязка объявляется в разметке, для задания источника можно использовать свойство ElementName.
Как мы только что увидели, механизм связывания можно применить для привязки свойства Text (
<Window ... Title=’ExampleBind2’ >
<StackPanel>
<TextBox x:Name=’textBox1’ />
<TextBox x:Name=’textBox2’ />
<ContentControl
Margin=’5’
Content=’{Binding ElementName=textBox1,Path=Text}’
FontFamily=’{Binding ElementName=textBox2,Path=Text}’/>
</StackPanel>
</Window>
Существует два механизма преобразования: класс TypeConverter, существующий в .NET, начиная с версии 1.0, и новый интерфейс IValueConverter. В нашем случае с классом FontFamily ассоциирован конвертер типов TypeConverter, поэтому преобразование выполняется автоматически.
Чтобы выполнить нужное преобразование, можно воспользоваться конвертерами значений, ассоциированными с привязкой. Для этого берется источник (строка из свойства Text) и преобразуется в какой-то объект, который понимает получатель (свойство Content).
Начнем с создания простого типа:
public class Human
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Тип мог бы быть любым: встроенным, библиотечным, разработанным вами. Идея в том, что мы хотим преобразовать свойство Text в объект конкретного типа. Для этого произведем конвертер от интерфейса IValueConverter и реализуем два метода:
public class HumanConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
Human h = new Human();
h.Name = (string)value;
return h;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
return ((Human)value).Name;
}
}
В более сложных случаях реализовать преобразование в обе стороны может оказаться невозможно. Последний шаг при использовании конвертера – ассоциировать его с привязкой:
<ContentControl
Margin=’5’
FontFamily=’{Binding ElementName=textBox2,Path=Text}’>
<ContentControl.Content>
<Binding ElementName=’textBox1’ Path=’Text’>
<Binding.Converter>
<l:HumanConverter xmlns:l=’clr_namespace:ExampleBind’/>
</Binding.Converter>
</Binding>
</ContentControl.Content>
</ContentControl>
Элементы данных и преобразования – две базовые конструкции механизма связывания. Познакомившись с основными ингредиентами данных, мы можем заняться деталями привязки к объектам CLR.
Данные привязываются к объектам CLR с помощью свойств и списков (списком считается любой тип, реализующий интерфейс
Идентификатор имени свойства для связывания с объектом может записываться в двух видах: для простых свойств CLR и для зависимых свойств, производных от класса DependencyProperty. Чтобы понять, в чем разница, начнем с простого примера:
<Window
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<StackPanel>
<TextBox Name=’text1’>Hello</TextBox>
<TextBox Text=’{Binding ElementName=text1, Path=Text}’ />
</StackPanel>
</Window>
Здесь свойство Text объекта TextBox связывается со свойством Text другого объекта. С очень похожим примером мы уже встречались выше. Поскольку Text –зависимое свойство, то этот пример в точности эквивалентен такому:
<Window
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<StackPanel>
<TextBox Name=’text1’>Hello</TextBox>
<TextBox Text=’{Binding ElementName=text1, Path=(TextBox.Text)}’ />
</StackPanel>
</Window>
Во втором случае мы воспользовались формой идентификатора свойства, "классифицированной классом". Результат в обоих случаях одинаков, но во втором примере удается избежать применения отражения для разрешения имени "Text" в выражении привязки. Эта оптимизация полезна с двух точек зрения: во-первых, чтобы избежать накладных расходов на отражение, а, во-вторых, чтобы обеспечить привязку к присоединенным свойствам. Например, если бы мы захотели привязать объект TextBox к свойству Grid.Row, то могли бы написать,
<SomeControl SomeProperty=’{Binding ElementName=text1, Path=(Grid.Row)}’ />.
Чтобы лучше понять, как работают пути к свойствам, мы можем взять чуть более сложный объект. Определим класс Person, в котором есть составные свойства Address и Name. В совокупности три класса – Person, Name и Address – образуют небольшую объектную модель, на которой можно продемонстрировать некоторые интересные задачи, возникающие в связи со связыванием. Для начала организуем простое отображение данных о человеке
<!— Window1.xaml —>
<Window ... Title=’Object Binding’>
<StackPanel>
<ContentControl Content=’{Binding Path=Name}’ />
<TextBlock Text=’{Binding Path=Addresses[0].AddressName}’ />
<TextBlock Text=’{Binding Path=Addresses[0].Street1}’ />
<TextBlock Text=’{Binding Path=Addresses[0].City}’ />
</StackPanel>
</Window>
// Window1.xaml.cs
public partial class Window1: Window
{
public Window1()
{
InitializeComponent();
DataContext = new Person();
new Name("Иван", "Иванов"),
new Address("Интуит",
"Интуит.ru",
"Москва");
}
}
Здесь иллюстрируется привязка к простому свойству (Path=Name) и более сложные пути к свойствам (Path=Addresses[0].AddressName). Квадратные скобки позволяют добраться до отдельных элементов набора. Обратите также внимание, что мы можем составить сколь угодно сложный путь из имен свойств и индексов в списке. Привязка к списку производится точно так же, как к свойству. Путь к свойству должен приводить к объекту, который реализует интерфейс
<ListBox ItemsSource=’{Binding Path=Addresses}’ />
У человека есть единственное имя и нуль или более адресов. До сих пор нас интересовало в основном отображение данных. Если у свойства, к которому мы привязываемся, есть метод установки, то возможна и двусторонняя привязка.
Чтобы редактировать значения, должен быть какой-то способ узнать, что значение изменилось. Помимо разрешения изменять свойство, существует несколько интерфейсов, которые позволяют объекту или списку рассылать извещения об изменении. Если источник данных уведомляет об изменении, то система связывания сможет отреагировать на модификацию данных. Чтобы наделить наш класс Person способностью извещать об изменениях, у нас есть три возможности:
Использование событий для извещения об изменениях свойств впервые было применено в .NET 1.0 и поддерживается механизмами связывания в Windows Forms и ASP.NET. Интерфейс INotifyPropertyChanged появился в .NET 2.0. Он оптимизирован для привязки к данным, обладает более высокой производительностью и проще как для авторов объектов, так и для самой системы связывания. Но использовать этот интерфейс в обычных сценариях, когда необходимо извещать об изменениях, несколько сложнее.
Применение свойств, производных от DependencyProperty, относительно просто, позволяет объекту воспользоваться преимуществами, которые дает разреженное хранилище, и хорошо сопрягается с другими службами WPF (например, с динамической привязкой к ресурсам и стилизацией). Подробно создание объектов со свойствами, производными от DependencyProperty, обсуждается в лекции о User/
Все три способа во время выполнения демонстрируют одинаковое поведение. Вообще говоря, при создании модели данных лучше реализовать интерфейс INotifyPropertyChanged. Для использования свойств, производных от DependencyProperty, требуется, чтобы класс объекта наследовал DependencyObject, а это, в свою очередь, означает, что объект данных должен работать в
public class Name: INotifyPropertyChanged
{
...
public string First
{
get { return _first; }
set
{
_first = value;
NotifyChanged("First");
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
void NotifyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Раз мы применяем один и тот же TextBox для редактирования значений. Поскольку класс Name реализует интерфейс INotifyPropertyChanged, то при изменении значений система связывания получит извещение, что приведет к обновлению объектов TextBlock:
<Window ... Text=’Object Binding’>
<StackPanel>
<TextBlock Text=’{Binding Path=Name.First}’ />
<TextBlock Text=’{Binding Path=Name.Last}’ />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Row=’0’ Grid.Column=’0’>First</Label>
<TextBox Grid.Row=’0’ Grid.Column=’1’
Text=’{Binding Path=Name.First}’ />
<Label Grid.Row=’1’ Grid.Column=’0’>Last</Label>
<TextBox Grid.Row=’1’ Grid.Column=’1’
Text=’{Binding Path=Name.Last}’ />
</Grid>
</StackPanel>
</Window>
Ввод в любое текстовое поле обновляет соответствующую область окна. Отметим, что изменение происходит только при выходе из поля. По умолчанию элемент TextBox обновляет данные только в момент потери фокуса. Чтобы изменить это поведение, нужно указать, что привязка должна обновляться при любом изменении значения свойства. Для этого служит свойство привязки UpdateSourceTrigger:
<TextBox Grid.Row=’1’ Grid.Column=’1’
Text=’{Binding Path=Name.Last, UpdateSourceTrigger=PropertyChanged}’/>
Списки сложнее простого изменения свойств. Для связывания необходимо знать, какие элементы были добавлены или удалены. С этой целью в WPF введен интерфейс INotifyCollectionChanged, в котором определено единственное событие CollectionChanged с аргументом такого типа:
public class NotifyCollectionChangedEventArgs: EventArgs
{
public NotifyCollectionChangedAction Action { get; }
public IList NewItems { get; }
public int NewStartingIndex { get; }
public IList OldItems { get; }
public int OldStartingIndex { get; }
}
public enum NotifyCollectionChangedAction
{
Add,
Remove,
Replace,
Move,
Reset,
}
В нашем примере мы можем поддержать динамическое добавление и удаление адресов человека. Проще всего это реализуется в помощью класса ObservableCollection<T>, который наследует Collection<T> и дополнительно реализует интерфейс INotifyCollectionChanged:
public class Person: INotifyPropertyChanged
{
IList<Address> _addresses = new ObservableCollection<Address>();
...
}
Теперь можно модифицировать наше приложение так, чтобы все адреса отображались в списковом поле, и реализовать пользовательский интерфейс для ввода новых адресов и добавления их в список:
<!— window1.xaml —>
...
<StackPanel>
<ListBox ItemsSource=’{Binding Path=Addresses}’/>
<Button Click=’Add’ Grid.Row=’4’>Add</Button>
</StackPanel>
…
// window1.xaml.cs
void Add(object sender, RoutedEventArgs e)
{
Address a = new Address();
a.Street1 = _street.Text;
a.City = _city.Text;
((Person)DataContext).Addresses.Add(a);
}
В текстовые поля можно вводить новую информацию, а при нажатии кнопки Add список адресов обновляется. Поскольку свойство Addresses реализует интерфейс INotifyCollectionChanged, система связывания получает извещение о новом адресе и корректно обновляет пользовательский интерфейс.
Привязка к объектам CLR – процедура по большей части автоматическая. Реализовав интерфейсы INotifyPropertyChanged и INotifyCollectionChanged, мы наделяем источник данных "интеллектом".
Data access object (DAO) — это объект, который предоставляет абстрактный интерфейс к какому-либо типу базы данных или механизму хранения. Определённые возможности предоставляются независимо от того, какой механизм хранения используется и без необходимости специальным образом соответствовать этому механизму хранения. Этот шаблон проектирования применим ко множеству языков программирования, большинству программного обеспечения, нуждающемуся в хранении информации и к большей части баз данных, но традиционно этот шаблон связывают с приложениями на платформе Java Enterprise Edition, взаимодействующими с реляционными базами данных через интерфейс JDBC, потому что он появился в рекомендациях от фирмы Sun Microsystems.
Remote Data Objects (RDO) — технология доступа к базам данных компании Microsoft. Представляет собой набор COM-объектов инкапсулирующих ODBCAPI, а также клиентскую курсорную библиотеку. Технология
ActiveX Data Objects (ADO) — интерфейс программирования приложений для доступа к данным, разработанный компанией Microsoft (MS Access, MS SQL Server) и основанный на технологии компонентов ActiveX. ADO позволяет представлять данные из разнообразных источников (реляционных баз данных, текстовых файлов и т.д.) в объектно-ориентированном виде.
ADO.NET — основная модель доступа к данным для приложений, основанных на Microsoft .NET. Не является развитием более ранней технологии ADO. Скорее представляет собой совершенно самостоятельную технологию. Компоненты ADO.NET входят в поставку оболочки .NET Framework; таким образом, ADO.NET является одной из главных составных частей .NET.
Статическое связывание — связывание цели вызова и вызываемого метода на этапе компиляции, когда с сущностью связывается метод класса, заданного при объявлении сущности.
Динамическое связывание — связывание цели вызова и вызываемого метода на этапе выполнения, когда с сущностью связывается метод класса объекта, связанного с сущностью в момент выполнения.
В лекции мы рассмотрели, как в WPF устроена работа с данными приложения. Система привязки к данным глубоко интегрирована в платформу, и при наличии подходящей модели мы можем создавать приложения, целиком
Вопросы:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.