WPF and "Twistori": Part 6

Following on from this previous post there’s a few things wrong with where I’ve got to so far and some improvements that I’d like to make;

  1. I’d like to display something when there’s no new feed items available.
  2. I’d like some kind of transition when one feed item moves to another.
  3. I’d like to avoid displaying an empty user control when the application starts up. That is, this doesn’t look great;

image

whilst I’m waiting for the first item to come in and, finally,

  1. There’s a problem with images. I’m loading an image over the web and I need to know when it’s loaded otherwise the image goes on the screen before it’s really loaded and then updates whilst the user is looking at it and that doesn’t seem right at all to me ๐Ÿ™‚

So, 4 things to fix even if the last one is called (1) just like the first one – I can’t work list numbering in Live Writer ๐Ÿ™‚

Dealing with the first issue isn’t too difficult – my AtomFeedManager class fires a FeedItemUnavailable event so I could just add code to the handler for that which manufactures a “fake” item to display when there’s nothing else to display as in;

    void OnFeedItemUnavailable(object sender, AtomFeedItemUnavailableEventArgs args)
    {
      Dispatcher.BeginInvoke(new Action(() =>
      {
        displayControl.DataContext = new AtomFeedItem()
        {
          Author = "none",
          AuthorImageUri = new Uri("pack://application:,,,/question.jpg"),
          Title = string.Format("No activity for {0:0} seconds, check your options",
            args.UnavailableTime.TotalSeconds),
          PublishedDate = DateTime.Now
        };
      }));
    }

where I’m just making up a data item because none is available. The only “odd” thing about this is that I’ve added an existing image question.jpg to the project and set it to be built as a resource to embed it into the assembly and then I can then use the pack: URI in order to make use of that image.

This means that when there is no data I now see something like this;

image

so that’s one of my issue-list items sorted.

Thinking about what to do about the initial blank display – it seems simple to initially mark the control as hidden and only display it when the first feed item arrives (even if it is a made-up feed item when there are no feed items available).

That’s fine…however I still have the problem with images. How to know when an image is really ready for display because I don’t want to put my ItemDisplayControl onto the screen if it has still to load image for the author of the tweet from the web. I only want that thing to appear on the screen when the image is good and loaded.

For me, this introduces the idea that my ItemDisplayControl could fire some Ready style event when a user of the control can feel confident that it’s good to put the control onto the screen knowing that its image will have been loaded.

How do we know when a WPF image has been loaded from the network?

The Image doesn’t tell us this but the BitmapImage class does. Inside of my user control, I’m using an Image like this;

        <Image
          Stretch="Fill"
          MaxWidth="96"
          MaxHeight="96"
          Width="Auto"
          Height="Auto"
          Margin="2,2,2,2"
          Source="{Binding AuthorImageUri}">
        </Image>

so I’d assume that I can do something like;

        <Image
          Stretch="Fill"
          MaxWidth="96"
          MaxHeight="96"
          Width="Auto"
          Height="Auto"
          Margin="2,2,2,2">
          <Image.Source>
            <BitmapImage
              UriSource="Source={Binding AuthorImageUri}" />
          </Image.Source>
        </Image>

and then I could add handlers to the events like DownloadCompleted and DownloadFailed on that BitmapImage which is what I need.

However…I don’t think that I can directly bind that UriSource property like that ( see here ). Now, however I tried to do this with converters and so on I never quite managed to get this to work and so I resorted to code. I thought I’d handle the DataContextChanged event myself on the Image as in;

        <Image
          DataContextChanged="OnDataContextChanged"
          x:Name="imageAuthor"
          Stretch="Fill"
          MaxWidth="96"
          MaxHeight="96"
          Width="Auto"
          Height="Auto"
          Margin="2,2,2,2">

 

and then I can put some code behind that leaving my code for my ItemDisplayControl looking like;

  public partial class ItemDisplayControl : UserControl
  {
    public event EventHandler ControlReady;

    public ItemDisplayControl()
    {
      InitializeComponent();
    }
    void FireControlReady()
    {
      if (ControlReady != null)
      {
        ControlReady(this, null);
      }
    }
    private void OnImageDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
      AtomFeedItem feedItem = (AtomFeedItem)e.NewValue;

      // The item may not have an image, in which case...we are done.
      if (!feedItem.HasImage)
      {
        FireControlReady();
      }
      else if (!feedItem.HasImageNeedingDownload)
      {
        // Some images are packed (i.e. pack://) into the executable. No
        // need to download them.
        imageAuthor.Source = new BitmapImage(feedItem.AuthorImageUri);
        FireControlReady();
      }
      else
      {
        BitmapImage bi = new BitmapImage();
        bi.BeginInit();
        bi.UriSource = feedItem.AuthorImageUri;
        bi.DownloadCompleted += (s, args) =>
          {
            imageAuthor.Source = bi;
            FireControlReady();
          };
        bi.DownloadFailed += (s, args) =>
          {
            FireControlReady();
          };
        bi.EndInit();
      }
    }
  }

 

The use of this control in the main Window then changes. Initially, I want it to be invisible and I want to handle its ControlReady event. Here’s the updated snippet of XAML from the main UI which declares the control;

    <local:ItemDisplayControl
      x:Name="displayControl"
      Visibility="Hidden"
      ControlReady="OnItemDisplayReady" 
      Grid.Column="1"
      HorizontalAlignment="Center"
      VerticalAlignment="Center" />

and then I can handle that ControlReady event in my OnItemDisplayReady handler by just changing the visibility;

    void OnItemDisplayReady(object sender, EventArgs args)
    {
      ShowItemDisplayControl();
    }
    void ShowItemDisplayControl()
    {
      displayControl.Visibility = Visible;
    }

and I can make sure that I hide the control whenever we change its data context thereby forcing it to load a new image which is in the handlers for items becoming available/unavailable;

    void OnFeedItemAvailable(object sender, AtomFeedItemAvailableEventArgs args)
    {
      Dispatcher.BeginInvoke(new Action(() =>
        {
          HideItemDisplayControl();
          displayControl.DataContext = args.FeedItem;
          listSearchTerms.SelectedItem = args.SearchTerm;
          listSearchTerms.ScrollIntoView(args.SearchTerm);
        }));
    }
    void OnFeedItemUnavailable(object sender, AtomFeedItemUnavailableEventArgs args)
    {
      Dispatcher.BeginInvoke(new Action(() =>
      {
        HideItemDisplayControl();
        displayControl.DataContext = new AtomFeedItem()
        {
          Author = "none",
          AuthorImageUri = new Uri("pack://application:,,,/question.jpg"),
          Title = string.Format("No activity for {0:0} seconds, check your options",
            args.UnavailableTime.TotalSeconds),
          PublishedDate = DateTime.Now
        };
      }));
    }
    void HideItemDisplayControl()
    {
      displayControl.Visibility = Visibility.Hidden;
    }

and that all works quite nicely.

So, I’ve solved items (1), (3) and (4) of my original list – all I’d like now is some kind of transition between one item leaving and the next one arriving. I’ll leave that to the next post.

In the meantime, the bits as they currently stand are here for download.

Posted in WPF