Thanks to all the people who came to our re-run of the MSDN Roadshow that went around the country earlier in the year – this was a bunch of sessions on;
- ADO.NET Entity Framework and Data Services
- ASP.NET MVC, Dynamic Data and other enhancements in VS 2008 Sp1
- Silverlight V2
- Visual Studio Team System
and happened in our London office today.
My slides on Silverlight V2 are here for download in PPTX format – other formats will no appear on the post events site.
A few questions that came up that needed code-samples as answers ( interestingly, these were both WPF ). These were put together very quickly so apply a pinch of salt as I haven’t spent a lot of time here.
1) Can ObservableCollection<T> cope with a particular element being replaced? Not Added. Not Deleted. Replaced.
Answer is “yes”. Not 100% sure if I’ve got the question right but if I’ve got a UI that looks like this;
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <StackPanel> <ListBox x:Name="lstPeople" ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Margin="5,0,5,0" Text="{Binding FirstName}" /> <TextBlock Text="{Binding LastName}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Content="Click" Click="OnClick" /> </StackPanel> </Window>
and I back it up with code like this;
public class Person { public string FirstName { get; set; } public string LastName { get; set; } } namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); this.Loaded += new RoutedEventHandler(Window1_Loaded); } void OnClick(object sender, EventArgs args) { // Change one of the values in the collection... people[0] = new Person() { FirstName = "First3", LastName = "Last3" }; } void Window1_Loaded(object sender, RoutedEventArgs e) { people = new ObservableCollection<Person>(); people.Add(new Person() { FirstName = "First1", LastName = "Last1" }); people.Add(new Person() { FirstName = "First2", LastName = "Last2" }); lstPeople.DataContext = people; } ObservableCollection<Person> people; } }
then the UI does the right thing at the point where we replace people[0] with a different object in response to the button press.
2) How to work with lookup fields in a database and data-bind to them?
Let’s imagine I’ve got a simple database schema that looks like this (I’m not saying that this is necessarily the way to do lookup tables) – note that this is in my database called demo;
create table gender ( id int identity not null primary key, value nchar(10) not null ) create table hairColour ( id int identity not null primary key, value nchar(10) not null ) create table person ( id int identity primary key not null, firstName nvarchar(30) not null, lastName nvarchar(30) not null, gender int not null foreign key references gender(id), hairColour int not null foreign key references hairColour(id) )
and I put some test data in it like this;
insert gender (value) values('male') insert gender (value) values('female') insert hairColour values('black') insert hairColour values('brown') insert hairColour values('red') insert hairColour values('blonde') insert Person values('miss', 'piggy', 2, 3) insert Person values('fozzy', 'bear', 1, 2)
Now, I want to display this on a form with some comboboxes for the lookups which are gender and hairColour and I want to do that with databinding. I’m sure there’s many ways of doing this but I thought that one way would be to write a class that knows how to do the lookup and then use that from XAML.
I can use LINQ to SQL to access my data as in;
and that gives me a class called DemoDataContext to work with.
I could then go and write a class such as this one ( which really should be doing some caching but isn’t right now although that’s somewhat mitigated by the way I’m intending to use it );
class DatabaseStringLookup { class ResultType { [Column] public int Key { get; set; } [Column] public string Value { get; set; } } public DatabaseStringLookup() { } public string Table { get; set; } public string KeyColumn { get; set; } public string ValueColumn { get; set; } public Dictionary<int, string> Values { get { Dictionary<int, string> values; using (DemoDataContext ctx = new DemoDataContext()) { string query = string.Format("select [{0}] as [Key], [{1}] as Value from [{2}]", KeyColumn, ValueColumn, Table); values = ctx.ExecuteQuery<ResultType>(query).ToDictionary( rt => rt.Key, rt => rt.Value); } return (values); } } }
and then I can use that from XAML in my app.xaml for the application ( note that my application is called WpfApplication1 );
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" StartupUri="Window1.xaml"> <Application.Resources> <local:DatabaseStringLookup x:Key="lookupGender" Table="gender" KeyColumn="id" ValueColumn="value" /> <local:DatabaseStringLookup x:Key="lookupHairColour" Table="hairColour" KeyColumn="id" ValueColumn="value" /> </Application.Resources> </Application>
which is causing the instantiation of two instances of DatabaseStringLookup which I can then use as in;
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="600" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="4*" /> <RowDefinition /> </Grid.RowDefinitions> <ListBox x:Name="lstPeople" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <Border BorderBrush="Black" BorderThickness="2" CornerRadius="2" Margin="5"> <Grid Margin="5"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock VerticalAlignment="Center" Text="First Name" /> <TextBlock VerticalAlignment="Center" Text="Last Name" Grid.Row="1" /> <TextBlock VerticalAlignment="Center" Text="Gender" Grid.Row="2" /> <TextBlock VerticalAlignment="Center" Text="Hair Colour" Grid.Row="3" /> <TextBox Text="{Binding firstName}" HorizontalAlignment="Stretch" Grid.Column="1" /> <TextBox Text="{Binding lastName}" HorizontalAlignment="Stretch" Grid.Column="2" Grid.Row="1" /> <ComboBox Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Source={StaticResource lookupGender}, Path=Values}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding gender}" /> <ComboBox Grid.Column="1" Grid.Row="3" ItemsSource="{Binding Source={StaticResource lookupHairColour}, Path=Values}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding hairColour}" /> </Grid> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Grid.Row="1" Content="Save Data" Click="OnSaveData" Margin="5" FontSize="14" /> </Grid> </Window>
and I’m trying to bind the displayed items and displayed values of the combo boxes in my ListBox using my DatabaseStringLookup class. This seems to work fine and just needs lining up with a bit of code behind ( but very little );
namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); this.Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { context = new DemoDataContext(); lstPeople.DataContext = context.persons.ToList(); } void OnSaveData(object sender, EventArgs args) { // Note: Really needs exception handling. context.SubmitChanges(); } DemoDataContext context; } }
and ( as far as I can tell ) we’re in business giving me a UI like;
3) How to take action when an ItemsControl creates an item and get hold of the item’s UI elements?
There’s a bunch of posts out there on this kind of thing;
How can I get a ListBoxItem from a data bound ListBox-
What is best practise for databinding custom controls with xmlFile from xaml –