WPF and "Twistori": Part 3

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;

image

adding a reference to my library for using twitter feeds;

image

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;

image

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;

image

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;

image

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.