Printer Friendly Version      Send     
Click to Rate and Give Feedback
MSDN
MSDN Library
.NET Development
Acropolis CTP 1
 Walkthrough: Building an Acropolis ...
Walkthrough: Building an Acropolis RSS Reader Application
[Note: This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.]

This walkthrough demonstrates how to use the CTP1 release of Code Name "Acropolis" to create a complex part that reads RSS feeds and displays articles. To create the RSS reader part, you will integrate an existing RSS content service and an existing HTML document part for rendering articles. You will also create and integrate an RSS feed part to encapsulate the HTML document parts. For an overview of "Acropolis", see Acropolis Overview.

The user interface for the completed RssPart will contain the following items:

  • A combo box navigator control similar to the Internet Explorer address bar.

  • A PartPane that can host the view of the FeedPart.

The RssPart will contain one child FeedPart for each valid RSS feed in the combo box navigator drop-down list. When the user selects a feed, the PartPane will display the corresponding FeedPart. The FeedPart, in turn, will contain one child HtmlDocPart for each article in the feed.

In this walkthrough, you perform the following tasks:

  • Create the application shell.

  • Add the existing RssContentService and HtmlDocPart projects.

  • Create the RssPart and FeedPart.

  • Implement the parts by adding connection points and service dependencies in the designer, and adding business logic to the code-behind files.

  • Implement the Windows Presentation Foundation (WPF) views for these parts.

  • Add the completed RssPart to the application shell.

  • Improve the user interface with animated transitions and view switching.

This walkthrough is based on the RssEagle sample located in your Documents folder under Microsoft Acropolis CTP1 Samples.

NoteNote:

The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Visual Studio Settings.

You need the following components to complete this walkthrough:

  • Windows Vista or Windows XP Service Pack 2

  • Microsoft Internet Explorer 7 (already included with Windows Vista)

  • Visual Studio Code Name "Orcas" Beta 1.

  • Code Name "Acropolis" CTP1.

In the following procedure, you create a new "Acropolis" application shell project and configure it using the Acropolis Application Setup Wizard.

NoteNote:

To ensure the best results when completing this walkthrough, you should clear the "Acropolis" Toolbox cache before starting Visual Studio. To clear the cache, delete the contents of the C:\Users\YourUserName\AppData\Local\Microsoft\VisualStudio\9.0\AcropolisToolbox folder. Restart Visual Studio, if necessary.

To create the application shell

  1. In Visual Studio, select the File | New | Project menu option.

    The New Project dialog box appears.

  2. In the Project types pane, expand Visual Basic or Visual C# and select the Acropolis node.

  3. In the Templates pane, confirm that the WPF Application template is selected.

  4. Name the project RssReader and then click OK.

    The Acropolis Application Setup Wizard appears.

    Acropolis Application Wizard
  5. In the left pane, click Layout and then clear the Status Bar check box.

  6. Click Finish.

    The RssReader project opens in Visual Studio.

The new "Acropolis" project contains the following classes:

  • A class named Application to contain the business logic of the application. This class derives from AcropolisApplication, which in turn derives from the WPF Application class.

  • A class named Window1 to contain the user interface of the application. This class derives from Shell, which in turn derives from the WPF Window class.

A new "Acropolis" project has a structure similar to that of a new WPF project. Like the application and window files in a new WPF project, the Application and Window1 classes are implemented as XAML files with Visual Basic or C# code-behind files.

The code-behind files are nested under the XAML files in Solution Explorer. The XAML files represent configuration files that can be edited with a designer (either the "Acropolis" part designer or the WPF designer).

The RssPart and FeedPart that you will create in the next section both have a dependency on an RSS content service. This sample will use an existing service rather than building one from scratch. It will also use an existing part that can display a single article retrieved from an RSS feed. This demonstrates the composite nature of "Acropolis" applications and the reusability of "Acropolis" components. The composite approach enables different teams to build different parts of an application, and enables you to share libraries of completed parts for use in many applications.

In the following procedure, you add references to the RssContentService and HtmlDocPart components, plus two additional components on which they depend. The Microsoft Feeds library enables the RssContentService to retrieve the RSS feeds currently stored in Microsoft Internet Explorer. The HtmlToXamlConverter component enables the HtmlDocPart to display article content using an alternate part view, as described in the "Improving the User Interface" section near the end of this walkthrough.

To add existing components to your project

  1. In Solution Explorer, right-click the RssReader project and select Add Reference.

  2. In the Add Reference dialog box, click the Browse tab and then navigate to the Microsoft Acropolis CTP1 Samples\Bin folder in your Documents folder.

  3. While pressing the CTRL key, select RssContentService.dll, HtmlDocPart.dll, and HtmlToXamlConverter.dll, and then click OK.

  4. In Solution Explorer, right-click the RssReader project and select Add Reference.

  5. In the Add Reference dialog box, click the COM tab, select Microsoft Feeds, version 1.0, and then click OK.

In the following procedure, you add the RssPart and FeedPart and their part views to the project.

To create the RssPart and FeedPart

  1. In Solution Explorer, right-click the RssReader project and select Add | New Item.

    The Add New Item dialog box appears.

  2. Select the Acropolis Part and View (WPF) template, change the name to RssPart.xaml and then click Add.

    The RssPart appears in the "Acropolis" part designer.

  3. Repeat this process to add another Acropolis Part and View (WPF) to the project, setting the name to FeedPart.xaml.

    The FeedPart appears in the designer.

  4. Build the solution. This ensures that the two new parts will appear in the Toolbox.

    NoteNote:

    Currently, Toolbox items will occasionally fail to appear or will fail to work when you double-click them. If you encounter issues with the Toolbox or the "Acropolis" Designer in the steps later in this topic, find the related element in the complete XAML provided after each designer procedure. Then, copy the element into the XAML view in the appropriate location.

Each new part includes a XAML file and a code-behind file nested underneath it in Solution Explorer. Each part also includes a part view with its own XAML file and code-behind file.

In the following procedure, you configure the RssPart by using the "Acropolis" part designer and by modifying the RssPart.xaml file.

To configure the RssPart

  1. In Solution Explorer, double-click RssPart.xaml.

    The RssPart opens in the part designer.

  2. In the Toolbox, expand the Acropolis Framework tab.

    NoteNote:

    The Toolbox will be empty if XAML view has the focus. To restore the Toolbox contents in this case, click in Design view.

    Also, the "Acropolis" designer will occasionally stop working correctly or just display the "Whoops!" screen. To restore the designer, close and reopen the file causing the problems.

  3. Double-click ComponentCommand.

    The connection point appears in the part designer.

  4. In the Properties window, set the Name property of the connection point to urlNotFound.

  5. In XAML view, find the ComponentCommand element and add the following attribute to assign a handler to the CommandExecuted event. You implement this event handler in the next procedure.

    CommandExecuted="urlNotFound_CommandExecuted"
  6. Click in Design view to give it the focus.

  7. In the Toolbox, double-click IUIService Dependency.

    The service dependency appears in the part designer.

  8. In the Properties window, set the Name property of the service dependency to UIService and select IsRequired.

  9. In XAML view, add the following element to the AcropolisComponent.ServiceDependencies element after the element for the UIService dependency. This indicates that the part requires an RSS service that conforms to the indicated IRssContentService interface.

    NoteNote:

    Adding XAML is required for this step because the RSS service is located in an external assembly. Toolbox support for components in external assemblies is planned for a future version of "Acropolis".

    <ServiceDependency Name="rssService" IsRequired="True"
      ServiceType="{x:Type my:IRssContentService}" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />
  10. Click in Design view to give it the focus.

  11. In the ToolBox in the Acropolis Framework tab, double-click SinglePartNavigationManager to add a navigation manager to the design surface.

  12. In XAML view, find the Afx:SinglePartNavigationManager element and add the following attributes to assign a handler to the ActiveItemsChanging event and to prevent child parts from activating automatically.

    ActiveItemsChanging="navigationManager_ActiveItemsChanging" 
    ActivateOnAdd="False"
  13. Select the File | Save All menu option.

The following illustration shows the relevant portion of the design surface for the completed RssPart. Notice the warning icons for the service dependencies, which indicate that dependencies must be fulfilled before the part will be functional.

RssPart in Acropolis Part Designer

The following code shows the completed RssPart.xaml file.

NoteNote:

In Visual Basic, the x:Class attribute will be set to "RssPart", excluding the RssReader namespace.

<Afx:Part x:Class="RssReader.RssPart"
  xmlns="clr-namespace:Microsoft.Acropolis.CommonFx;assembly=Microsoft.Acropolis.CommonFx"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Afx="clr-namespace:Microsoft.Acropolis.PartFx;assembly=Microsoft.Acropolis.PartFx"
  x:Name="RssPart" Title="RssPart">

  <Afx:Part.NavigationManager>
    <Afx:SinglePartNavigationManager
      x:Name="SinglePartNavigationManager" 
      ActiveItemsChanging="navigationManager_ActiveItemsChanging"
      ActivateOnAdd="False" />
  </Afx:Part.NavigationManager>

  <Afx:Part.ChildParts>
  </Afx:Part.ChildParts>
  
  <AcropolisComponent.ConnectionPoints>
    <ComponentCommand Name="urlNotFound" 
      CommandExecuted="urlNotFound_CommandExecuted"/>
  </AcropolisComponent.ConnectionPoints>

  <AcropolisComponent.ServiceDependencies>
    <ServiceDependency Name="UIService" 
      ServiceType="{x:Type Afx:IUIService}" IsRequired="True" />
    <ServiceDependency Name="rssService" IsRequired="True"
      ServiceType="{x:Type my:IRssContentService}" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />
  </AcropolisComponent.ServiceDependencies>

</Afx:Part>

In the following procedure, you add code to the RssPart code-behind file to implement the part's business logic.

To implement the RssPart business logic

  1. In Solution Explorer, expand the RssPart.xaml node and then double-click the code-behind file (RssPart.xaml.vb or RssPart.xaml.cs).

  2. Add the following statements to the top of the file.

    Visual Basic
    Imports System.Globalization
    Imports Microsoft.Acropolis.Samples.RssEagle.RssContent
    

    C#
    using System.Globalization;
    using Microsoft.Acropolis.Samples.RssEagle.RssContent;
    
  3. Add the following code after the RssPart constructor. This code adds a child FeedPart for each RSS feed that the RSS content service retrieves from Internet Explorer.

    Visual Basic
    Protected Overrides Sub OnFactoryCreationComplete()
    
        MyBase.OnFactoryCreationComplete()
    
        'Prepopulate the Navigation Manager with URLs from IE Store.
        Dim index As Integer
    
        If RssService.SavedFeeds IsNot Nothing Then
            Dim rssUrl As String
            For Each rssUrl In RssService.SavedFeeds
                index = Me.ChildParts.Add(GetType(FeedPart), rssUrl)
    
                ' Setting what the Navigator will see
                Me.NavigationManager.NavigationModel(index).Title = rssUrl
            Next rssUrl
        Else
            ShowErrorMessage("Error while retrieving saved feeds.")
        End If
    
    End Sub 
    

    C#
    protected override void OnFactoryCreationComplete()
    {
        base.OnFactoryCreationComplete();
    
        //Prepopulate the Navigation Manager with URLs from IE Store.
        int index;
    
        if (RssService.SavedFeeds != null)
        {
            foreach (string rssUrl in RssService.SavedFeeds)
            {
                index = this.ChildParts.Add(typeof(FeedPart), rssUrl);
    
                // Setting what the Navigator will see
                this.NavigationManager.NavigationModel[index].Title =
                    rssUrl;
            }
        }
        else
        {
            ShowErrorMessage(
                "Error while retrieving saved feeds.");
        }
    }
    
  4. Add the following code after the previous code. This code handles the NavigationManager.ActiveItemsChanging event to validate the RSS feed when the user selects a new one in the combo box navigator control. The event handler calls the VerifyUri method to check the RSS feed, and cancels navigation if the feed is not valid. The VerifyUri method uses the RSS content service to perform the actual validation, and returns the result to its caller. Additionally, if the feed is not valid, the VerifyUri method calls the ShowErrorMessage method, which uses the UI service to display an error message.

    Visual Basic
    Private Sub navigationManager_ActiveItemsChanging(ByVal sender _
        As Object, ByVal e As ActivatedItemsChangingEventArgs)
    
        Dim currItem As NavigationItem = e.ActivatingItems(0)
        If Not VerifyUri(currItem.Name) Then
            e.Cancel = True
        End If
    
    End Sub
    
    Private Function VerifyUri(ByVal urlString As String) As Boolean
    
        Dim validUri As Boolean = _
            RssService.IsValidFeedUrl(urlString)
    
        If Not validUri Then
            ShowErrorMessage(String.Format( _
                CultureInfo.InvariantCulture, _
                "Invalid URL: '{0}'", urlString))
        End If
    
        Return validUri
    
    End Function
    
    Private Sub ShowErrorMessage(ByVal mainMessage As String)
    
        Dim processingInfo As RssContentService.FeedProcessingInfo = _
            Nothing
    
        If Me.RssService.Messages.Count > 0 Then
            processingInfo = Me.RssService.Messages( _
                Me.RssService.Messages.Count - 1)
        End If
    
        If processingInfo IsNot Nothing Then
            mainMessage = String.Format(CultureInfo.InvariantCulture, _
                "{0}{1}{1}{2}{1}{3}", mainMessage, _
                Environment.NewLine, "Additional error information: ", _
                processingInfo.Message)
        End If
    
        UiService.Show(mainMessage, "RssReader", _
            MessageBoxButton.OK, MessageBoxImage.Error)
    
    End Sub
    

    C#
    private void navigationManager_ActiveItemsChanging(object sender, 
        ActivatedItemsChangingEventArgs e)
    {
        NavigationItem currItem = e.ActivatingItems[0];
        if (!VerifyUri(currItem.Name))
        {
            e.Cancel = true;
        }
    }
    
    private bool VerifyUri(string urlString)
    {
        bool validUri = RssService.IsValidFeedUrl(urlString);
        if (!validUri)
        {
            ShowErrorMessage(string.Format(
                CultureInfo.InvariantCulture,
                "Invalid URL: '{0}'", urlString));
        }
    
        return validUri;
    }
    
    private void ShowErrorMessage(string mainMessage)
    {
        RssContentService.FeedProcessingInfo processingInfo = null;
        if (this.RssService.Messages.Count > 0)
        {
            processingInfo = this.RssService.Messages[
                this.RssService.Messages.Count - 1];
        }
    
        if (processingInfo != null)
        {
            mainMessage = string.Format(CultureInfo.InvariantCulture,
                "{0}{1}{1}{2}{1}{3}", mainMessage, Environment.NewLine,
                "Additional error information: ",
                processingInfo.Message);
        }
    
        UIService.Show(mainMessage, "RssReader",
            MessageBoxButton.OK, MessageBoxImage.Error);
    }
    
  5. Add the following code after the previous code. This code provides a handler for the urlNotFound command connection point. When the command is executed, the handler validates the specified URL using the VerifyUri method, and adds a child FeedPart to the RssPart if the URL is a valid RSS feed. If the feed is not valid, the command does not do anything.

    Visual Basic
    Private Sub urlNotFound_CommandExecuted(ByVal sender As Object, _
        ByVal e As ComponentCommandExecutionEventArgs(Of Object))
    
        Dim url As String = e.Parameter.ToString()
    
        If VerifyUri(url) Then
            Dim index As Integer = _
                Me.ChildParts.Add(GetType(FeedPart), url)
            Me.NavigationManager.NavigationModel(index).Title = url
        End If
    End Sub
    

    C#
    private void urlNotFound_CommandExecuted(object sender, 
        ComponentCommandExecutionEventArgs<object> e)
    {
        string url = e.Parameter.ToString(); 
        if (VerifyUri(url))
        {
            int index = this.ChildParts.Add(typeof(FeedPart), url);
            this.navigationManager.NavigationModel[index].Title = url;
        }
    }
    
  6. Select the File | Save All menu option.

In the following procedure, you configure the FeedPart by using the "Acropolis" part designer and by modifying the FeedPart.xaml file. The FeedPart has the same service dependencies as the RssPart, but has a different command connection point.

To configure the FeedPart

  1. In Solution Explorer, double-click FeedPart.xaml.

    The FeedPart opens in the part designer.

  2. In the Toolbox, expand the Acropolis Framework tab.

  3. Double-click ComponentCommand to add a command connection point to the design surface.

  4. In the Properties window, set the Name property of the connection point to LoadChannel.

  5. In XAML view, find the ComponentCommand element and add the following attribute to assign a handler to the CommandExecuted event. You implement this event handler in the next procedure.

    CommandExecuted="loadChannel_CommandExecuted"
  6. Click in Design view to give it the focus.

  7. In the Toolbox, double-click IUIService Dependency.

    The service dependency appears in the part designer.

  8. In the Properties window, set the Name property of the service dependency to UIService and select IsRequired.

  9. In XAML view, add the following element to the AcropolisComponent.ServiceDependencies element after the element for the UIService dependency. This indicates that the FeedPart has the same RSS service dependency as its parent RssPart.

    <ServiceDependency Name="rssService" IsRequired="True"
      ServiceType="{x:Type my:IRssContentService}" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />
  10. Select the File | Save All menu option.

The design surface for the completed FeedPart is similar to the one for the RssPart, differing only in the names of the part and the command connection point.

The following code shows the completed FeedPart.xaml file.

NoteNote:

In Visual Basic, the x:Class attribute will be set to "RssPart", excluding the RssReader namespace.

<Afx:Part x:Class="RssReader.FeedPart"
  xmlns="clr-namespace:Microsoft.Acropolis.CommonFx;assembly=Microsoft.Acropolis.CommonFx"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Afx="clr-namespace:Microsoft.Acropolis.PartFx;assembly=Microsoft.Acropolis.PartFx"
  x:Name="FeedPart" Title="FeedPart">

  <Afx:Part.ChildParts>
  </Afx:Part.ChildParts>
  
  <AcropolisComponent.ConnectionPoints>
    <ComponentCommand Name="LoadChannel" 
      CommandExecuted="loadChannel_CommandExecuted"/>
  </AcropolisComponent.ConnectionPoints>

  <AcropolisComponent.ServiceDependencies>
    <ServiceDependency Name="UIService" 
      ServiceType="{x:Type Afx:IUIService}" IsRequired="True" />
    <ServiceDependency Name="rssService" IsRequired="True"
      ServiceType="{x:Type my:IRssContentService}" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />
  </AcropolisComponent.ServiceDependencies>

</Afx:Part>

In the following procedure, you add code to the FeedPart code-behind file to implement the part's business logic.

To implement the FeedPart business logic

  1. In Solution Explorer, expand the FeedPart.xaml node and then double-click the code-behind file (FeedPart.xaml.vb or FeedPart.xaml.cs).

  2. Add the following statements to the top of the file.

    Visual Basic
    Imports System.Globalization
    Imports Microsoft.Acropolis.Samples.RssEagle.RssContent
    Imports System.Collections.Generic
    

    C#
    using System.Globalization;
    using Microsoft.Acropolis.Samples.RssEagle.RssContent;
    using System.Collections.Generic;
    
  3. Add the following code to the FeedPart class before the constructor.

    Visual Basic
    Private Const NumberOfBlogsToDisplay As Integer = 4
    Private channel As IRssChannel
    Private loadedEntries As New List(Of IRssItem)()
    Private rssItemComparer1 As New RssItemComparer()
    

    C#
    const int NumberOfBlogsToDisplay = 4;
    IRssChannel channel;
    List<IRssItem> loadedEntries = new List<IRssItem>();
    RssItemComparer rssItemComparer = new RssItemComparer();
    
  4. Add the following code to the FeedPart class after the constructor. This code loads an RSS feed and then retrieves the number of articles specified by the NumberOfBlogsToDisplay constant. These articles are added to the user interface as HtmlDocPart child parts by using the AddNewArticle method described in the next step.

    Visual Basic
    Private Sub loadChannel_CommandExecuted(ByVal sender As Object, _
        ByVal e As ComponentCommandExecutionEventArgs(Of Object))
    
        ' Load the channel for this part when the part is activated.
        channel = GetRssChannel()
        If channel IsNot Nothing Then
            Me.AddNewArticles(channel)
        End If
    
    End Sub
    
    ' Gets the channel from the RssService.
    Private Function GetRssChannel() As IRssChannel
    
        Dim currChannel As IRssChannel = Nothing
        Dim uri As Uri = GetUri(Me.Name)
    
        If uri IsNot Nothing Then
            ' Get the channel now that the URI has been validated.
            currChannel = Me.RssService.GetChannel(uri, False)
        End If
    
        If currChannel Is Nothing Then
            Me.HandleError(Me.Name, False)
        End If
    
        Return currChannel
    
    End Function
    
    ' Gets the Uri from the UrlString.
    Private Function GetUri(ByVal urlString As String) As Uri
    
        Dim uriRet As Uri = Nothing
    
        Try
            uriRet = New Uri(urlString)
        Catch
            'invalid URL format
            Me.HandleError(urlString, True)
        End Try
    
        Return uriRet
    
    End Function
    
    ' Loop through and add each ArticlePart
    Private Sub AddNewArticles(ByVal channel As IRssChannel)
    
        Dim numberOfEntriesToDisplay As Integer = NumberOfBlogsToDisplay
    
        Dim i As Integer
        For i = 0 To channel.Items.Count - 1
    
            If i >= numberOfEntriesToDisplay Then
                Exit For
            End If
    
            Dim entry As IRssItem = channel.Items(i)
            Dim index As Integer = Me.loadedEntries.BinarySearch( _
                entry, rssItemComparer1)
            If index < 0 Then
                'keep them sorted
                Me.loadedEntries.Insert(Not index, entry)
                Me.AddNewArticle(entry)
            End If
    
        Next i
    
    End Sub
    
    Private Sub HandleError(ByVal urlString As String, _ 
        ByVal fInvalidUrl As Boolean)
    
        ' Get the last feed processing information entry.
        Dim processingInfo As _
            RssContentService.FeedProcessingInfo = Nothing
        If Me.RssService.Messages.Count > 0 Then
            processingInfo = Me.RssService.Messages( _
                Me.RssService.Messages.Count - 1)
        End If
    
        Dim message As String = Nothing
        If fInvalidUrl Then
            message = String.Format(CultureInfo.InvariantCulture, _
                "Invalid URL: '{0}'.", urlString)
        Else
            message = String.Format(CultureInfo.InvariantCulture, _
                "Could not find any RSS feeds at the URL that you " + _
                "provided: '{0}'. To locate RSS feeds navigate to " + _
                "a page with IE and click ALT+J. Then copy the " + _
                "blog URL to {1}.", urlString, "RssReader")
        End If
    
        If Not (processingInfo Is Nothing) Then
            message = String.Format(CultureInfo.InvariantCulture, _
                "{0}{1}{1}{2}{1}{3}", message, Environment.NewLine, _
                "Additional error information: ", _ 
                processingInfo.Message)
        End If
    
        ' Use the UIService to display messages in a 
        ' UI agnostic fashion. 
        UIService.Show(message, "RssReader", MessageBoxButton.OK, _
            MessageBoxImage.Error)
    
    End Sub
    
    ' Sorts blog entries by date.
    Private Class RssItemComparer
        Implements IComparer(Of IRssItem)
    
        Public Function Compare(ByVal x As IRssItem, _
            ByVal y As IRssItem) As Integer _
            Implements IComparer(Of IRssItem).Compare
    
            Return Uri.Compare(x.Url, _ 
                y.Url, UriComponents.AbsoluteUri, _
                UriFormat.Unescaped, _
                StringComparison.InvariantCultureIgnoreCase)
    
        End Function
    
    End Class
    

    C#
    private void loadChannel_CommandExecuted(object sender, 
        ComponentCommandExecutionEventArgs<object> e)
    {
        // Load the channel for this part when the part is activated.
        channel = GetRssChannel();
        if (channel != null)
        {
            this.AddNewArticles(channel);
        }
    }      
    
    // Gets the channel from the RssService.
    private IRssChannel GetRssChannel()
    {
        // FeedPart assumes it is being passed the 
        // correct FeedUrl.
        IRssChannel currChannel = null;
        Uri uri = GetUri(this.Name);
       
        if (uri != null)
        {
            // Get the channel now that the URI has been validated.
            currChannel = this.RssService.GetChannel(uri, false);
        }
    
        if (currChannel == null)
        {
            this.HandleError(this.Name, false);
        }
    
        return currChannel;
    }
    
    // Gets the Uri from the UrlString.
    private Uri GetUri(string urlString)
    {
        Uri uriRet = null;
        try
        {
            uriRet = new Uri(urlString);
        }
        catch (UriFormatException)
        {
            // Invalid URL format.
            this.HandleError(urlString, true);
        }
        return uriRet;
    }
    
    // Loop through and add each ArticlePart.
    private void AddNewArticles(IRssChannel channel)
    {
        int numberOfEntriesToDisplay = NumberOfBlogsToDisplay;
    
        for (int i = 0; i < channel.Items.Count && 
            i < numberOfEntriesToDisplay; i++)
        {
            IRssItem entry = channel.Items[i];
            int index = this.loadedEntries.BinarySearch(
                entry, rssItemComparer);
            if (index < 0)
            {
                // Keep them sorted.
                this.loadedEntries.Insert(~index, entry);
                this.AddNewArticle(entry);
            }
        }
    }
    
    private void HandleError(string urlString, bool fInvalidUrl)
    {
        // Get the last feed processing information entry.
        RssContentService.FeedProcessingInfo processingInfo = null;
        if (this.RssService.Messages.Count > 0)
        {
            processingInfo = this.RssService.Messages[
                this.RssService.Messages.Count-1];
        }
    
        string message = null;
        if (fInvalidUrl)
        {
            message = string.Format(CultureInfo.InvariantCulture, 
                "Invalid URL: '{0}'.", urlString);
        }
        else
        {
            message = string.Format(CultureInfo.InvariantCulture, 
            "Could not find any RSS feeds at the URL that you " +
            "provided: '{0}'. To locate RSS feeds navigate to a " +
            "page with Internet Explorer and click ALT+J. " +
            "Then copy the blog URL to {1}.", urlString, "RssReader");
        }
    
        if (processingInfo != null)
        {
            message = string.Format(CultureInfo.InvariantCulture, 
                "{0}{1}{1}{2}{1}{3}",
                message, 
                Environment.NewLine, 
                "Additional error information: ", 
                processingInfo.Message);
        }
    
        // Use the UIService to display messages in a 
        // UI agnostic fashion. 
        UIService.Show(message, "RssReader",
            MessageBoxButton.OK, MessageBoxImage.Error);
    }
    
    // Sorts blog entries by date.
    private class RssItemComparer : IComparer<IRssItem>
    {
        public int Compare(IRssItem x, IRssItem y)
        {
            return Uri.Compare(x.Url, y.Url, 
                UriComponents.AbsoluteUri, UriFormat.Unescaped,
                StringComparison.InvariantCultureIgnoreCase);
        }
    }
    
  5. Add the following code to the FeedPart class after the previous code. This code adds a generic HtmlDocPart as a child part of the FeedPart. It then retrieves the child part's contract, which provides access to the child's part-to-part connection points. Finally, this code configures the child part by setting the connection point values by using data from the RSS article.

    Visual Basic
    Private Sub AddNewArticle(ByVal rssItem As IRssItem)
    
        Dim uniqueArticleName As String = rssItem.Url.ToString()
    
        ' Add each post using a generic HtmlDocPart.            
        Dim index As Integer = _
            Me.ChildParts.Add(GetType(HtmlDocPart.HtmlDocPart), _
            uniqueArticleName)
        Dim post As HtmlDocPart.HtmlDocPart = _
            CType(Me.ChildParts(index), HtmlDocPart.HtmlDocPart)
    
            ' Establish the contract.
        Dim postContract As HtmlDocPart.IHtmlDocPartPartContract = _
            post.GetContract(Of HtmlDocPart.IHtmlDocPartPartContract)()
    
        ' Configure the HtmlDocPart with the associated values.
        post.Title = rssItem.Title
        postContract.Author.Value = rssItem.Author
        postContract.Date.Value = rssItem.LastUpdatedDate
        postContract.HtmlContent.Value = rssItem.Description
    
    End Sub
    

    C#
    private void AddNewArticle(IRssItem rssItem)
    {                                  
        string uniqueArticleName = rssItem.Url.ToString();
        // Add each post using a generic HtmlDocPart.            
        int index = this.ChildParts.Add(
            typeof(HtmlDocPart.HtmlDocPart), uniqueArticleName);            
        HtmlDocPart.HtmlDocPart post = 
            (HtmlDocPart.HtmlDocPart)this.ChildParts[index]; 
    
        // Establish the contract.
        HtmlDocPart.IHtmlDocPartPartContract postContract = 
            post.GetContract<HtmlDocPart.IHtmlDocPartPartContract>();
    
        // Configure the HtmlDocPart with the associated values.
        post.Title = rssItem.Title;
        postContract.Author.Value = rssItem.Author;
        postContract.Date.Value = rssItem.LastUpdatedDate;
        postContract.HtmlContent.Value = rssItem.Description;
    }
    
  6. Select the File | Save All menu option.

In the following procedure, you implement the XAML portion of the RssPartView.

To implement the RssPartView in XAML

  1. In Solution Explorer, double-click RssPartView.xaml.

  2. In XAML view, replace the default XAML with the following.

    <Afx:PartView 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Afx="clr-namespace:Microsoft.Acropolis.Windows;assembly=Microsoft.Acropolis.Windows"        
      xmlns:Navigators="clr-namespace:Microsoft.Acropolis.Windows.Navigators;assembly=Microsoft.Acropolis.Windows"        
      xmlns:Transitions="clr-namespace:Microsoft.Acropolis.Windows.Transitions;assembly=Microsoft.Acropolis.Windows"        
      x:Class="RssReader.RssPartView"
      Width="Auto" Height="Auto" 
      Background="#C1D3F4">
      <!-- UI -->
      <Grid VerticalAlignment="Stretch" Margin="5">
        <Grid.RowDefinitions>  
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="5"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Navigators:ComboBoxNavigator Name="navigator" Grid.Row="0" 
          IsEditable="True"
          NavigationItemNotFound="navigator_NavigationItemNotFound"
          NavigationManager="{Binding Part.NavigationManager}"/>
        <Afx:PartPane Grid.Row="2"
          Part="{Binding Part.NavigationManager.ActivePart}"
          ShowChrome="False" Background="White" >
          <!--<Afx:PartPane.Transition>-->
            <!--<Transitions:RotateTransition 
              TransitionEmptyContent="true"/>-->
            <!--<Transitions:RotateTransition 
              TransitionEmptyContent="true" Angle="0" 
              Direction="Up"/>-->
            <!--<Transitions:FadeTransition Duration="0:0:5.0" 
              TransitionEmptyContent="true"/>-->
            <!--<Transitions:RollTransition Duration="0:0:5.0" 
              TransitionEmptyContent="true"/>-->
          <!--</Afx:PartPane.Transition>-->
        </Afx:PartPane>
      </Grid>
    </Afx:PartView>
  3. In Visual Basic, find the x:Class attribute of the Afx:PartView element, and then remove the RssReader namespace. The attribute should look like the following when you are finished:

    x:Class="RssPartView"
    

In the following procedure, you implement the code-behind portion of the RssPartView.

To implement the RssPartView code-behind

  1. In Solution Explorer, right-click RssPartView.xaml and select View Code.

    The RssPartView code-behind file appears in the code editor.

  2. Add the following statement to the top of the file.

    Visual Basic
    Imports Microsoft.Acropolis.Windows.Navigators
    

    C#
    using Microsoft.Acropolis.Windows.Navigators;
    
  3. Add the following code to the RssPartView class after the constructor. This code uses the part's UrlNotFound command connection point to respond when the user specifies an invalid RSS feed. The commands implementation (described earlier) double-check the URL and adds the feed to the part only if the feed is valid.

    Visual Basic
    Private Sub navigator_NavigationItemNotFound(ByVal sender As _ 
        Object, ByVal e As NavigationItemNotFoundEventArgs)
    
        Me.Part.UrlNotFound.Execute(e.Text)
    
    End Sub
    

    C#
    private void navigator_NavigationItemNotFound(object sender, 
        NavigationItemNotFoundEventArgs e)
    {
        this.Part.UrlNotFound.Execute(e.Text);
    }
    

In the following procedure, you will implement the XAML portion of the FeedPartView.

To implement the FeedPartView in XAML

  1. In Solution Explorer, double-click FeedPartView.xaml.

  2. In XAML view, replace the default XAML with the following. The Afx:SplitLayoutPane element displays the child parts in a vertical series of resizable, collapsible panes. Because the PartInsertLocation attribute is set to "Last", each new child part is added to the end of the list, ensuring that they are displayed in order. The Afx:SplitLayoutPane.PartPaneStyle element specifies a few property values that you can use for the part view of each child part that gets added to the FeedPart. (For more information about the last, commented-out Setter element, see the "Next Steps" section at the end of this walkthrough.)

    <Afx:PartView 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Afx="clr-namespace:Microsoft.Acropolis.Windows;assembly=Microsoft.Acropolis.Windows" 
      xmlns:HtmlDocPart="clr-namespace:HtmlDocPart;assembly=HtmlDocPart"
      x:Class="RssReader.FeedPartView"
      Width="Auto" Height="Auto" > 
      <!-- UI -->
      <Afx:SplitLayoutPane Padding="0" AllowDrop="True" 
        Parts="{Binding Part.NavigationManager.ActiveParts}" 
        PartInsertLocation="Last"
        ShowChrome="False">
        <Afx:SplitLayoutPane.PartPaneStyle>
          <Style TargetType="Afx:PartPane"
            BasedOn="{StaticResource {x:Type Afx:PartPane}}">
            <Setter Property="ShowCommands" Value="False"/>
            <Setter Property="ToolTip" Value="{Binding Path=Title}"/>
            <Setter Property="PaneState" Value="Minimized"/>
            <!--<Setter Property="PartViewType" 
              Value="{x:Type HtmlDocPart:HtmlDocPartWpfView}"/>-->
          </Style>
        </Afx:SplitLayoutPane.PartPaneStyle>
      </Afx:SplitLayoutPane>
    </Afx:PartView>
  3. In Visual Basic, find the x:Class attribute of the Afx:PartView element, and then remove the RssReader namespace. The attribute should look like the following when you are finished:

    x:Class="FeedPartView"
    

In the following procedure, you implement the code-behind portion of the FeedPartView.

To implement the FeedPartView code-behind

  1. In Solution Explorer, right-click FeedPartView.xaml and select View Code.

    The FeedPartView code-behind file appears in the code editor.

  2. Add the following code to the FeedPartView class after the constructor. This code loads the RSS feed as soon as the view is finished binding to the FeedPart. To load the feed, the view uses the part's LoadChannel command connection point.

    Visual Basic
    Public Overrides Sub BindToContract(ByVal contract As _ 
        IPartViewContract)
    
        MyBase.BindToContract(contract)
        Me.Part.LoadChannel.Execute(Nothing)
    End Sub
    

    C#
    public override void BindToContract(IPartViewContract contract)
    {
        base.BindToContract(contract);
        this.Part.LoadChannel.Execute(null);
    }
    

The final step to completing the RssReader sample is to add the RssPart to the application. In the following you will modify the Application.xaml file to add the RssPart as a child part of the application shell.

To add the RSS part to the application shell

  1. Build the solution so that the RssPart will appear in the Toolbox.

  2. In Solution Explorer, double-click Application.xaml.

    The Application class appears in the designer.

  3. Click in Design view.

  4. In the Toolbox, expand the User Components (Acropolis) tab.

  5. Double-click RssPart to add the part as a child of the application shell.

  6. In XAML view, add the following element to the AcropolisApplication.Services element. This indicates that the application shell contains the RSS content service that the RssPart requires.

    <my:RssContentService Name="RssContentService1" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />

The following illustration shows the relevant portion of the design surface for the application. Notice that because the RssContentService fulfills the service dependencies of the child RssPart, the warning icons have been replaced with check marks.

Acropolis Application Designer with RssPart

The following code shows the completed Application.xaml file.

<AcropolisApplication x:Class="RssReader.Application"
  xmlns="clr-namespace:Microsoft.Acropolis.Windows;assembly=Microsoft.Acropolis.Windows"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Acx="clr-namespace:Microsoft.Acropolis.CommonFx;assembly=Microsoft.Acropolis.CommonFx"
  xmlns:Afx="clr-namespace:Microsoft.Acropolis.PartFx;assembly=Microsoft.Acropolis.PartFx"
  Theme="Microsoft Acropolis Theme"
  StartupUri="Window1.xaml">

  <AcropolisApplication.ChildParts>
    <my:RssPart Name="RssPart1" 
      xmlns:my="clr-namespace:RssReader;assembly=RssReader" />
  </AcropolisApplication.ChildParts>

  <AcropolisApplication.NavigationManager>
    <Afx:SinglePartNavigationManager 
      x:Name="SinglePartNavigationManager"/>
  </AcropolisApplication.NavigationManager>

  <AcropolisApplication.ConnectionPoints>
    <Acx:ComponentCommand x:Name="ExitCommand" 
      Handles="AcropolisCommands.Exit"
      CommandExecuted="OnAppExit" 
      Description="Close all open documents and quit the application"/>
  </AcropolisApplication.ConnectionPoints>
  
  <AcropolisApplication.Services>
    <my:RssContentService Name="RssContentService1" 
      xmlns:my="clr-namespace:Microsoft.Acropolis.Samples.RssEagle.RssContent;assembly=RssContentService" />
  </AcropolisApplication.Services>

</AcropolisApplication>

You can now build and run the RssReader application. When you run the application, you will see the RssPart with its combo box navigator control and empty child FeedPart. The following procedure describes how to test the part.

To test the RssReader application

  1. In Visual Studio, press F5 to run the application.

  2. Select a feed from the combo box drop-down list, or type a new feed URL, such as one of the following:

    • http://blogs.msdn.com/brada/rss.xml

    • http://blogs.msdn.com/somasegar/rss.xml

    • http://blogs.msdn.com/kathykam/rss.xml

    • http://blogs.msdn.com/dphill/rss.xml

    • http://blogs.msdn.com/acropolis/rss.xml

  3. Click the arrow button next to the combo box.

    If the feed is valid, it loads the FeedPart with four articles by default. You can expand, collapse, and resize the article panes.

  4. When finished, close the application.

The following illustration shows the running application.

RssReader Application

Because of the composite nature of "Acropolis", you can improve the RssReader application in many ways by adding additional or alternative parts, part views, and services.

For example, you can replace the part view for the HtmlDocPart so that the article content is rendered in WPF. By default, the HtmlDocPart uses a WindowsFormsHost to embed a Windows Forms WebBrowser control in the WPF view so that the articles can be rendered in their native HTML. Unfortunately, WindowsFormsHost content always paints on top of WPF content, as you can see when you open multiple articles at the same time.

The following procedure describes how to replace the default part view for the HtmlDocPart children of the FeedPart. The new part view makes use of the HtmlToXamlConverter that you added as an assembly reference earlier in this walkthrough.

To replace the part view for the HtmlDocPart

  1. In Solution Explorer, double-click FeedPartView.xaml.

  2. In XAML view, find the Afx:SplitLayoutPane.PartPaneStyle element, which contains a child Style element.

  3. Uncomment the last Setter element in the Style element. This sets the PartViewType property to a view type that renders articles using an HtmlToXamlConverter component used by the HtmlDocPart project.

  4. Press F5 to run the application.

  5. Open a feed and view the articles. The articles now appear in the WPF document style, and you can open multiple articles without adversely affecting the display.

You can also improve the user interface by adding animated transitions. The following procedure describes how to experiment with transitions.

To add an animated transition

  1. In Solution Explorer, double-click RssPartView.xaml.

  2. In XAML view, find the Afx:PartPane.Transition element.

  3. Uncomment the element's open and closing tags and one of the child transition elements provided as examples.

  4. Press F5 to run the application, and then open an RSS feed to view the transition.

Another way to improve the user interface is by elaborating or replacing the shell window with XAML created by Microsoft Expression Blend. For an example of this, see the full RssEagle sample located in your Documents folder under Microsoft Acropolis CTP1 Samples. The following illustration shows the full sample.

RssEagle Application
© 2008 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement