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

98

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Запрос из базы данных

Рассмотрим простое окно, показанное на рисунке ниже. Оно позволяет пользователю указывать код машины, после чего отображает соответствующую информацию о машине в контейнере Grid (в нижней части окна):

Запрос машины

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

Ниже приведен код разметки для отображения объекта CarTable:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>

            <TextBlock Margin="7">Car ID:</TextBlock>
            <TextBox Name="txtID" Margin="5" Grid.Column="1"/>
            <Button Click="cmdGetCar_Click" Margin="5" Padding="2" Grid.Column="2">Получить данные</Button>
        </Grid>

        <Border Grid.Row="1" Padding="7" Margin="7" Background="LightSteelBlue">
            <Grid  Name="gridCarDetails">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="*"></RowDefinition>
                </Grid.RowDefinitions>

                <TextBlock Margin="7">Марка:</TextBlock>
                <TextBox Margin="5" Grid.Column="1" Text="{Binding Path=ModelName}"></TextBox>
                <TextBlock Margin="7" Grid.Row="1">Модель:</TextBlock>
                <TextBox Margin="5" Grid.Row="1" Grid.Column="1" Text="{Binding Path=ModelNumber}"></TextBox>
                <TextBlock Margin="7" Grid.Row="2">Цена (руб):</TextBlock>
                <TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=Cost}"></TextBox>
                <TextBlock Margin="7,7,7,0" Grid.Row="3">Описание:</TextBlock>
                <TextBox Margin="7" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
                         VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" Text="{Binding Path=Description}"></TextBox>
            </Grid>
        </Border>
</Grid>

Обратите внимание, что контейнер Grid, служащий оболочкой для всех этих деталей, имеет имя, так что им можно манипулировать в коде и завершить привязку данных.

При первом запуске приложения никакая информация не отображается. Даже если определить необходимые привязки, никакого объекта источника не доступно. Когда пользователь щелкает на кнопке во время выполнения, извлекаются данные о соответствующей модели машины. Хотя каждую привязку можно создать программно, это не имеет особого смысла (и не сэкономит много кода по сравнению с заполнением элементов управления вручную).

Однако свойство DataContext предлагает блестящую альтернативу. Если установить его для контейнера Grid, содержащего все выражения привязки данных, то все выражения будут использовать его для заполнения себя данными.

Ниже приведен код обработки событий щелчка на кнопке пользователем:

private void cmdGetCar_Click(object sender, RoutedEventArgs e)
{
            int id;

            if (Int32.TryParse(txtID.Text, out id))
                gridCarDetails.DataContext = MainWindow.GetAutoById(id);
            else
                MessageBox.Show("Неверный формат ID");
}

Текущий класс CarTable предполагает получение полного комплекта данных о машине. Однако таблицы базы данных часто включают поля, допускающие значения null, которые указывают на отсутствующую или неприменимую информацию. Это отражается в классах данных с помощью допускающих null типов данных для простых числовых значений или дат. Например, в сущностном классе CarTable можно использовать int? вместо int. Естественно, ссылочные типы, такие как строки и полноценные объекты, всегда поддерживают значения null.

Результат привязки значения null предсказуем — целевой элемент вообще ничего не отображает. Для числовых полей это поведение полезно, поскольку позволяет отличать отсутствующее значение (когда элемент не показывает ничего) от нулевого значения (когда показывается текст 0). Тем не менее, следует отметить, что есть возможность изменить способ отображения WPF значений null, устанавливая свойство TargetNullValue в выражении привязки. Если сделать это, то вместо null будет отображаться указанное значение. Вот пример отображения текста [Описание не предоставлено], когда свойство CarTable.Decription равно null:

Text="{Binding Path=Description, TargetNullValue=[Описание не предоставлено]}"

Квадратные скобки в тексте TargetNullValue не обязательны. В данном случае они служат для подсказки пользователю, что отображаемый текст не взят из базы данных.

Обновление базы данных

Для того чтобы включить обновления базы данных в этом примере, дополнительно ничего делать не понадобится. Свойство TextBox.Text использует двустороннюю привязку по умолчанию. В результате объект CarTable модифицируется в случае редактирования содержимого текстовых полей. (Формально каждое свойство обновляется при переходе на новое поле, поскольку в качестве режима обновления источника по умолчанию для свойства TextBox.Text установлен LostFocus.)

Зафиксировать изменения в базе данных можно в любой момент. Все, что для этого понадобится — добавление метода обновления в обработчик события клика кнопки Обновить. При щелчке на ней код может получить текущий объект CarTable из контекста данных и воспользоваться им для фиксации обновления. Давайте добавим эту кнопку, видоизменив часть разметки:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        
        <Button Grid.Row="2" HorizontalAlignment="Left" Margin="7,0,7,5" Padding="2" Click="cmdUpdateCar_Click"
                Content="Обновить"></Button>
    
    ...
private void cmdUpdateCar_Click(object sender, RoutedEventArgs e)
{
            CarTable ct = (CarTable)gridCarDetails.DataContext;

            // Используем LINQ to Entities для обновления базы данных
            AutoShopEntities context = new AutoShopEntities();

            CarTable ct1 = context.CarTables
                                  .Where(p => p.ID == ct.ID)
                                  .Single<CarTable>();

            ct1.ModelName = ct.ModelName;
            ct1.ModelNumber = ct.ModelNumber;
            ct1.Cost = ct.Cost;
            ct1.Description = ct.Description;

            context.SaveChanges();
}

С этим примером связана одна потенциальная загвоздка. После щелчка на кнопке Обновить фокус переходит к этой кнопке и все незафиксированные изменения в текстовых полях применяются к объекту CarTable. Однако если сделать кнопку Обновить кнопкой по умолчанию (установив свойство IsDefault в true), появится другая возможность. Пользователь сможет внести изменения в одно из полей и нажать клавишу <Enter>, чтобы запустить процесс обновления без фиксации последнего изменения. Во избежание такой ситуации необходимо явно передать фокус, прежде чем выполнять любой код базы данных:

FocusManager.SetFocusedElement(this, (Button)sender); 
Пройди тесты
Лучший чат для C# программистов