Привязка данных и LINQ
98WPF --- Привязка, команды и стили WPF --- Привязка данных и LINQ
»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ
В WPF поддерживается язык интегрированных запросов (Language Integrated Query — LINQ), предлагающий синтаксис запросов общего назначения, который работает с широким разнообразием источников данных и тесно интегрирован в язык C#.
Язык LINQ работает с любым источником, для которого имеется соответствующий поставщик LINQ. Поддержка, включенная в .NET, дает возможность использовать одинаково структурированные запросы LINQ для извлечения данных из коллекции, находящейся в памяти, из файла XML либо из базы данных SQL Server. Как и другие языки запросов, LINQ позволяет фильтровать, сортировать и трансформировать извлекаемые данные.
Давайте предположим, что нужно добавить в созданное ранее приложение функциональность сортировки коллекции машин по стоимости. Ниже я приведу развернутый пример, который включает в себя все то, что мы изучили в предыдущих статьях с добавлением новой функциональности посредством LINQ (в проект потребуется добавить окно AddWindow.xaml):
// Вспомогательные методы в сущностном классе CarTable (файл AutoShopDataModel.Designer.cs)
public partial class CarTable : EntityObject
{
// Свойство, содержащее коллекцию объектов CarTable хранящихся в базе данных
public static ObservableCollection<CarTable> Cars
{
get
{
AutoShopEntities context = new AutoShopEntities();
return new ObservableCollection<CarTable>(
context.CarTables.Select(s => s).ToList());
}
}
public override string ToString()
{
return ModelName + " " + ModelNumber;
}
...
<!-- MainWindow.xaml -->
<Window x:Class="DataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:databinding="clr-namespace:DataBinding"
Title="База данных AutoShop" Height="650" Width="440">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ListBox Name="lstCars" Margin="5" />
<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal" Margin="5,2,5,10">
<Button Margin="2,0,0,0" Padding="2" Content="Удалить" Click="removeCar_Click"/>
<Button Margin="2,0,0,0" Padding="2" Content="Добавить" Click="addCar_Click"/>
<Button Margin="2,0,0,0" Padding="2" Content="Ошибки" Click="getErrors_Click"/>
</StackPanel>
<StackPanel Grid.Row="2" HorizontalAlignment="Right" Orientation="Horizontal" Margin="5,2,5,10">
<TextBlock>Max стоимость: </TextBlock>
<TextBox x:Name="txbMaxCost" Width="80" HorizontalContentAlignment="Center" Margin="8,0,8,0"/>
<Button Content="Получить" Click="getCarsMaxCost_Click"/>
</StackPanel>
</Grid>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" ResizeBehavior="PreviousAndNext" Height="5"/>
<Border Grid.Row="2" Padding="7" Margin="7" Background="LightSteelBlue">
<Border.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="16" FontWeight="Bold" Text="X"/>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder Name="adorner"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Resources>
<Grid Name="gridCarDetails" DataContext="{Binding ElementName=lstCars, Path=SelectedItem}">
<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" LostFocus="textChange_Event">
<TextBox.Text>
<Binding Path="ModelName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<databinding:EmptyRule></databinding:EmptyRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Margin="7" Grid.Row="1">Модель:</TextBlock>
<TextBox Margin="5" Grid.Row="1" Grid.Column="1" LostFocus="textChange_Event">
<TextBox.Text>
<Binding Path="ModelNumber" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<databinding:EmptyRule></databinding:EmptyRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Margin="7" Grid.Row="2">Цена (руб):</TextBlock>
<TextBox Margin="5" Grid.Row="2" Grid.Column="1"
Text="{Binding Cost, ValidatesOnExceptions=True}"
LostFocus="textChange_Event"></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, TargetNullValue=Описание не доступно}" LostFocus="textChange_Event"/>
</Grid>
</Border>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Collections.ObjectModel;
namespace DataBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lstCars.ItemsSource = CarTable.Cars;
}
// Обновляем список, соответствующий максимальной введенной стоимости
private void getCarsMaxCost_Click(object sender, RoutedEventArgs e)
{
try
{
Double d = Double.Parse(txbMaxCost.Text);
AutoShopEntities context = new AutoShopEntities();
// Используем LINQ
lstCars.ItemsSource = new ObservableCollection<CarTable>(
context.CarTables.Where(s => s.Cost <= d).Select(p => p));
}
catch
{
}
}
// Удалить запись из списка используя LINQ (без удаления из базы данных)
private void removeCar_Click(object sender, RoutedEventArgs e)
{
((ObservableCollection<CarTable>)lstCars.ItemsSource).Remove((CarTable)lstCars.SelectedItem);
}
// Добавить запись в коллекцию
private void addCar_Click(object sender, RoutedEventArgs e)
{
AddWindow awin = new AddWindow();
awin.ShowDialog();
if (awin.AddWinCarTable != null)
{
((ObservableCollection<CarTable>)lstCars.ItemsSource).Add(awin.AddWinCarTable);
}
}
// Запись данных в базу при редактировании полей (LINQ to Entities)
private void textChange_Event(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();
}
// Получение списка ошибок
private void getErrors_Click(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
GetErrors(sb, gridCarDetails);
string message = sb.ToString();
if (message != "") MessageBox.Show(message);
}
private void GetErrors(StringBuilder sb, DependencyObject obj)
{
foreach (object child in LogicalTreeHelper.GetChildren(obj))
{
TextBox element = child as TextBox;
if (element == null) continue;
if (Validation.GetHasError(element))
{
sb.Append(element.Text + " найдена ошибка:\r\n");
foreach (ValidationError error in Validation.GetErrors(element))
{
sb.Append(" " + error.ErrorContent.ToString());
sb.Append("\r\n");
}
}
GetErrors(sb, element);
}
}
}
// Специальное правило проверки достоверности полей ModelName и ModelNumber
public class EmptyRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (((string)value).Length == 0)
return new ValidationResult(false, "Пустое значение поля");
return new ValidationResult(true, null);
}
}
}
<!-- AddWindow.xaml -->
<Window x:Class="DataBinding.AddWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AddWindow" Height="420" Width="360" WindowStyle="None">
<Grid Background="LightSteelBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="10,5,20,5"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="10,5,5,5"/>
</Style>
</Grid.Resources>
<TextBlock Text="Id"/><TextBox x:Name="id" Grid.Column="1"/>
<TextBlock Text="ModelName" Grid.Row="1"/><TextBox x:Name="ModelName" Grid.Column="1" Grid.Row="1"/>
<TextBlock Text="ModelNumber" Grid.Row="2"/><TextBox x:Name="ModelNumber" Grid.Column="1" Grid.Row="2"/>
<TextBlock Text="CategoryId" Grid.Row="3"/><TextBox x:Name="CategoryId" Grid.Column="1" Grid.Row="3"/>
<TextBlock Text="CategoryName" Grid.Row="4"/><TextBox x:Name="CategoryName" Grid.Column="1" Grid.Row="4"/>
<TextBlock Text="Cost" Grid.Row="5"/><TextBox x:Name="Cost" Grid.Column="1" Grid.Row="5" />
<TextBlock Text="ImageCar" Grid.Row="6"/><TextBox x:Name="ImageCar" Grid.Column="1" Grid.Row="6"/>
<TextBlock Text="Description" Grid.Row="7"/><TextBox x:Name="Description" Grid.Column="1" Grid.Row="7"
VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" VerticalContentAlignment="Top"/>
<StackPanel Grid.Row="8" Orientation="Horizontal" HorizontalAlignment="Center" Grid.ColumnSpan="2">
<Button Margin="8,6" Padding="3" Content="Добавить" Click="Add_Click"/>
<Button Margin="0,6" Padding="3" Content="Отмена" Click="Cancel_Click"/>
</StackPanel>
</Grid>
</Window>
// AddWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace DataBinding
{
/// <summary>
/// Interaction logic for AddWindow.xaml
/// </summary>
public partial class AddWindow : Window
{
public CarTable AddWinCarTable { get; set; }
public AddWindow()
{
InitializeComponent();
}
private void Add_Click(object sender, RoutedEventArgs e)
{
try
{
AddWinCarTable = new CarTable
{
ID = Int32.Parse(id.Text),
CategoryID = Byte.Parse(CategoryId.Text),
CategoryName = CategoryName.Text,
ModelName = ModelName.Text,
ModelNumber = ModelNumber.Text,
Cost = Double.Parse(Cost.Text),
ImageCar = ImageCar.Text,
Description = Description.Text
};
this.Close();
}
catch
{
MessageBox.Show("Заданы некорректные данные");
}
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
AddWinCarTable = null;
this.Close();
}
}
}