Following on from my previous post, I wanted to create a WPF UI around what I’d done so far.
I’ll step through this one a little bit. Making a new project;
adding a reference to my library for using twitter feeds;
now I want my WPF Window to maximise itself on the screen and to be the topmost window so I’d play with settings to achieve that and change the Window title whilst I’m there (even if it’s not displayed).
<Window x:Class="BlogPostTwistori.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Mike's Twistori-Like App" Topmost="true" WindowState="Maximized" WindowStyle="None"> <Grid> </Grid> </Window>
Now maximised windows can be a pain to debug so I’d probably switch off Topmost whilst I’m working on the code and also it’d be nice to have a way to get rid of the application by pressing the ESCape key. If WPF defined an ApplicationCommands.Exit command then I think you’d be able to do this without writing any code but I don’t think it has such a command. So…we can create one;
static class CustomCommands { static CustomCommands() { exitCommand = new RoutedCommand("Exit", typeof(CustomCommands)); } public static RoutedCommand Exit { get { return (exitCommand); } } static RoutedCommand exitCommand; }
and then we can add a binding for it to our Window (notice that we’re bringing the local code namespace into the XAML here, I tend to use the prefix local for that);
<Window x:Class="BlogPostTwistori.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Mike's Twistori-Like App" xmlns:local="clr-namespace:BlogPostTwistori" Topmost="false" WindowState="Maximized" WindowStyle="None"> <Window.InputBindings> <KeyBinding Key="ESC" Command="{x:Static local:CustomCommands.Exit}" /> </Window.InputBindings> <Window.CommandBindings> <CommandBinding Command="{x:Static local:CustomCommands.Exit}" Executed="OnExit" /> </Window.CommandBindings> <Grid> </Grid> </Window>
and then we can add a handler for it;
public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void OnExit(object sender, ExecutedRoutedEventArgs e) { Application.Current.Shutdown(); } }
and so now we have a Window which can be closed by pressing the ESCape key (alongside the usual ALT+F4 and so on).
Ok, so setting up our main window so that it gets some data from the Twitter feeds;
public partial class Window1 : Window { public Window1() { InitializeComponent(); this.Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { // TODO: Handle exceptions from config loading? SearchTermsConfiguration config = SearchTermsConfiguration.LoadFromConfig(); feedManager = new AtomFeedManager(config, new TimeSpan(0, 0, 5)); feedManager.FeedItemAvailable += OnFeedItemAvailable; feedManager.FeedItemUnavailable += OnFeedItemUnavailable; } void OnFeedItemAvailable(object sender, AtomFeedItemAvailableEventArgs args) { } void OnFeedItemUnavailable(object sender, AtomFeedItemUnavailableEventArgs args) { } void OnExit(object sender, ExecutedRoutedEventArgs e) { Application.Current.Shutdown(); } AtomFeedManager feedManager; }
It would now be handy to have somewhere to display my list of search terms. Adding a ListBox and some grid columns to the UI;
<Window x:Class="BlogPostTwistori.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Mike's Twistori-Like App" xmlns:local="clr-namespace:BlogPostTwistori" Topmost="false" WindowState="Maximized" WindowStyle="None"> <Window.InputBindings> <KeyBinding Key="ESC" Command="{x:Static local:CustomCommands.Exit}" /> </Window.InputBindings> <Window.CommandBindings> <CommandBinding Command="{x:Static local:CustomCommands.Exit}" Executed="OnExit" /> </Window.CommandBindings> <Grid x:Name="mainGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.2*" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox Margin="10" ItemsSource="{Binding}" x:Name="listSearchTerms"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <CheckBox IsChecked="{Binding IsIncluded}" Content="{Binding Term}" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>
and I need to make sure that my OnLoaded function (or something) sets up the DataContext for that ListBox that’s called listSearchTerms as in this last line of code here;
void OnLoaded(object sender, RoutedEventArgs e) { // TODO: Handle exceptions from config loading? SearchTermsConfiguration config = SearchTermsConfiguration.LoadFromConfig(); feedManager = new AtomFeedManager(config, new TimeSpan(0, 0, 5)); feedManager.FeedItemAvailable += OnFeedItemAvailable; feedManager.FeedItemUnavailable += OnFeedItemUnavailable; listSearchTerms.DataContext = config; }
Ok…so I have something that runs and displays UI;
and that UI is bound to the right properties underneath so that should all work nicely. When a feed item arrives, I need something to display it in so let’s add a quick user control;
and let’s throw a little bit of UI in there as well;
<UserControl x:Class="BlogPostTwistori.ItemDisplayControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <Image Stretch="Fill" MaxWidth="96" MaxHeight="96" Source="{Binding AuthorImageUri}" /> <TextBlock Text="{Binding Author}" /> </StackPanel> <TextBlock Grid.Row="1" TextWrapping="Wrap" Text="{Binding Title}" /> </Grid> </UserControl>
And I’ll define one of these controls on my main UI as in;
<Window x:Class="BlogPostTwistori.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Mike's Twistori-Like App" xmlns:local="clr-namespace:BlogPostTwistori" Topmost="false" WindowState="Maximized" WindowStyle="None"> <Window.InputBindings> <KeyBinding Key="ESC" Command="{x:Static local:CustomCommands.Exit}" /> </Window.InputBindings> <Window.CommandBindings> <CommandBinding Command="{x:Static local:CustomCommands.Exit}" Executed="OnExit" /> </Window.CommandBindings> <Grid x:Name="mainGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.2*" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox Margin="10" ItemsSource="{Binding}" x:Name="listSearchTerms"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <CheckBox IsChecked="{Binding IsIncluded}" Content="{Binding Term}" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <local:ItemDisplayControl x:Name="displayControl" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid> </Window>
and then I just need to make sure that when a FeedItem arrives I set it as the DataContext for this user control as in;
void OnFeedItemAvailable(object sender, AtomFeedItemAvailableEventArgs args) { Dispatcher.BeginInvoke(new Action(() => { displayControl.DataContext = args.FeedItem; })); }
Note the switch to the dispatcher thread for this.
Ok…with that in place, I have a working application – ok, it looks like a complete bag of spanners but it’s working;
Anyway…in the next post it’d be time to pop open Expression Blend and see if we can’t make this look ever so slightly better.