|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionWPF introduced many new features to .NET development: separation of code and interface, data binding and styles are some of them. These new features allow the development of applications that reach a new level of user experience. The developer can concentrate on the business logic while the designer can create a UI that gives the user the best user experience. Another great feature introduced in WPF is data binding, where you can link object properties to the user interface with no need for code. This article will show how to use data binding and styles to show data coming from a Microsoft SQL database using the new object-relational model introduced in Visual Studio 2008, LINQ to SQL, allowing grouping, sorting and filtering of data with almost no code. RequirementsTo build the article project, you will need to use Visual Studio 2008 and .NET Framework 3.5 to use the new features introduced: LINQ, Lambda Expressions and type inference. You will also need to have Microsoft SQL Server 2005 installed, as we are using an SQL Server database. LINQ to SQLVisual Studio 2008 introduced a new technology named LINQ (Language Integrated Query), which allows a standard way to work with data, with the same query language, no matter where it comes from. It has several "flavors" that access data coming from different sources:
Using LINQ, you can query the data with the same syntax for any kind of data, using something like: var query = from d in ObjectCollection
select d;
In the command above, the The query language is very powerful, you can filter, sort and do aggregates (sum, count, average, etc.) on the data. LINQ to SQL allows the access to Microsoft SQL databases in an object-oriented way: with it, you have full access to the database, its tables, stored procedures and relations and still have the features available in Visual Studio, like debugging and Intellisense. You can also use data binding in WPF applications, and we will use this feature to show the database tables in a WPF To use LINQ to SQL, we must add a model to the project, using the "Add new item" project context menu item and selecting "LINQ to SQL classes". This will add a blank LINQ to SQL model. Dropping the tables from the server Explorer into the model will create a class model like the one shown in Figure 1.
Figure 1 - LINQ to SQL class model.
In this project we are using a Microsoft SQL Server database with two tables, To access this data we must create a LINQ query. This is done in the window constructor, in Window1.xaml.cs: BeatlesDataContext dc = new BeatlesDataContext();
var query = from s in dc.Songs
select s;
dataListBox.ItemsSource = query.ToList();
In the first line, we create a
Figure 2 - WPF Window using LINQ and data binding.
That's not what we expect: WPF doesn't know what to show, so it shows the result of the <ListBox x:Name="listBox1" Margin="10" DisplayMemberPath="Name"/>
Doing this will only show the songs' names in the list, but we want more than that: we want to show two lines for each song, one with the song name and duration and the other with the album name. We can do this using a template for the items in the list: <Window.Resources>
<!-- Data template for the listbox items -->
<DataTemplate x:Key="SongsTemplate">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<TextBlock Margin="5,0,0,0" Text="{Binding Path=Duration}"/>
</StackPanel>
<TextBlock Text="{Binding Path=Album.Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
As we said before, the relation between the <ListBox HorizontalAlignment="Stretch" Margin="5" Name="listBox1"
VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource SongsTemplate}"/>
Styles in the ApplicationUntil now, we have introduced some new LINQ concepts, but WPF has a lot more to offer. We can change the appearance for the <!-- Style for the Listbox items - Show border and content -->
<Style TargetType="ListBoxItem" x:Key="SongsItemContainerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="outsideBorder" Background="#FDF356"
Margin="2" CornerRadius="3" Padding="5"
BorderBrush="Black" BorderThickness="1" >
<ContentPresenter Margin="2" RecognizesAccessKey="True"
HorizontalAlignment="Stretch"/>
</Border>
<!-- Trigger when item is selected - change border stroke and background -->
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter TargetName="outsideBorder" Property="Background" Value="#FBA23A"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This <ListBox x:Name="dataListBox" Grid.Row="1" Margin="5"
ItemTemplate="{StaticResource SongsTemplate}"
ItemContainerStyle="{StaticResource SongsItemContainerStyle}" />
We have more data to show, and the best way to do it is to add a tooltip, so the extra information is shown when the mouse is over an item. The tooltip is also customizable, we can create a template for the tooltip with the data we want to show: <!-- Style for the tooltip - Show border and content -->
<Style TargetType="{x:Type ToolTip}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolTip}">
<Border Background="#698390" Opacity="0.95" Margin="2"
CornerRadius="3" Padding="5" BorderBrush="Black"
BorderThickness="1" >
<ContentPresenter Margin="10,5,10,5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextBlock.Foreground="Black" TextBlock.FontSize="12"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Placement" Value="Top" />
<Setter Property="HorizontalOffset" Value="20" />
</Style>
With this template, the tooltip is shown with rounded corners. The content for the tooltip is assigned in the Data template for the list items: <!-- Template for the tooltip - Show album image and extra data -->
<StackPanel.ToolTip>
<StackPanel Orientation="Horizontal">
<Border CornerRadius="2" BorderBrush="#FFFCF7" Padding="2"
BorderThickness="2">
<Image Width="117" Height="117"
Source="{Binding Path=Album.Cover,
Converter={StaticResource CoverConvert}}" />
</Border>
<StackPanel Margin="5" MaxWidth="600">
<StackPanel Margin="5" Orientation="Horizontal">
<TextBlock FontWeight="Bold"
Text="{Binding Path=Album.Name}" />
<TextBlock Margin="5,0,0,0" Text="(" />
<TextBlock Text="{Binding Path=Album.Year}"
HorizontalAlignment="Right" />
<TextBlock Text=")" />
</StackPanel>
<TextBlock Text="{Binding Path=Recording}"/>
<TextBlock Text="{Binding Path=RecordingPlace}"/>
<TextBlock Text="{Binding Path=Details,
Converter={StaticResource DetailConvert}}"
TextWrapping="Wrap"/>
</StackPanel>
</StackPanel>
</StackPanel.ToolTip>
In this tooltip we are showing a lot of data: the album cover image, the album name and year, the recording data, place and details for the song, but there is a catch: the database stores only the cover name, while the physical file also has the folder name and extension. To allow the binding of the cover name to the image source, we must create a converter that will take the name of the cover in the database and return a valid URI for the file. {
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return new Uri("..\\AlbumsBeatles\\" + value.ToString().Trim() +
"-A.jpg", UriKind.Relative);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
As we don't need the conversion from the URI to the cover name, we throw an exception for the "xmlns:src="clr-namespace:Views"
...
<!-- Converter for the cover name to Image URI -->
<src:NametoURIConverter x:Key="CoverConvert" />
The public class TabToNewLineConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().Replace('\t','\n').Trim();
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
It is declared with this XAML snippet: <!-- Converter for the details - converts tabs to new lines -->
<src:TabToNewLineConverter x:Key="DetailConvert" />
Sorting and Grouping DataOne way to sort the data is to change the LINQ query to add an BeatlesDataContext dc = new BeatlesDataContext();
var query = from s in dc.Songs
orderby s.Album.Name
select s;
dataListBox.ItemsSource = query.ToList();
or BeatlesDataContext dc = new BeatlesDataContext();
var query = from s in dc.Songs
select s;
dataListBox.ItemsSource = query.OrderBy(s =>s.Album.Name).ToList();
The expression The changed queries would require a new query to the database and would show the data in the new order, but that's not the best way to do it in WPF. WPF has the concept of ICollectionView view = CollectionViewSource.GetDefaultView(dataListBox.ItemsSource);
view.SortDescriptions.Clear();
view.SortDescriptions.Add(new SortDescription(
(sender as RadioButton).Tag.ToString(), ListSortDirection.Ascending));
That's enough to sort the list. The sorting order is determined by clicking on a <StackPanel Orientation="Horizontal" Grid.Row="1"
Background="Transparent">
<TextBlock Text="Sort by: " Margin="10,5" Foreground="Yellow"
FontWeight="Bold" />
<RadioButton x:Name="radioButton1" Tag="Name"
Click="RadioButton_Click" Content="Name" />
<RadioButton x:Name="radioButton2" Tag="Album.Name"
Click="RadioButton_Click" Content="Album" />
<RadioButton x:Name="radioButton3" Tag="Duration"
Click="RadioButton_Click" Content="Duration" />
<RadioButton x:Name="radioButton4" Tag="Album.Name"
Click="RadioButton_Click" Content="Grouped" />
</StackPanel>
The Grouping is almost the same as sorting: you must create a new view.GroupDescriptions.Clear();
if (sender == radioButton4)
{
view.GroupDescriptions.Add(
new PropertyGroupDescription("Album.Name"));
view.SortDescriptions.Add(new SortDescription("Name",
ListSortDirection.Ascending));
}
If we execute the code now, the grouping isn't shown. We must also add a <ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupTemplate}" />
</ListBox.GroupStyle>
The template for the group is: <!-- Data template for the group -->
<DataTemplate x:Key="GroupTemplate">
<Border Background="{StaticResource Brush_GroupBackground}"
CornerRadius="10" Height="Auto" Padding="10" Margin="5">
<StackPanel Orientation="Horizontal">
<Border CornerRadius="2" BorderBrush="#FFFCF7" Padding="2"
BorderThickness="2">
<Image Width="117" Height="117"
Source="{Binding Path=Items[0].Album.Cover,
Converter={StaticResource CoverConvert}}" />
</Border>
<TextBlock Text="{Binding Name}" Foreground="White"
FontFamily="Tahoma" FontSize="18" FontWeight="Bold"
VerticalAlignment="Center" Margin="5,0,0,0"/>
</StackPanel>
</Border>
</DataTemplate>
We are showing the
Figure 3 - Grouped data.
Filtering DataFiltering data in WPF is also very simple: we only have to pass a Lambda Expression to the private void filterBox_TextChanged(object sender, TextChangedEventArgs e)
{
ICollectionView view =
CollectionViewSource.GetDefaultView(dataListBox.ItemsSource);
view.Filter = m =>
((Song)m).Name.ToLower().Contains(filterBox.Text.ToLower());
}
The expression will return ConclusionWPF introduced great new capabilities to the .NET development: data binding and styles are just some of them. With these two, you can give to your users a new level of user experience, changing completely the way the data is shown and linking the presentation with data with almost no code-behind. This can be highly enhanced with sorting, grouping and filtering. Besides these capabilities, LINQ brings an Object-Relational model that fits nicely in the WPF data binding model: with these two technologies together, developing applications that interact with database data becomes a really easy task. History
|
|||||||||||||||||||||||||||||||||||||||||||||||