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
In Solution Explorer, double-click RssPart.xaml.
The RssPart opens in the part designer.
In the Toolbox, expand the Acropolis Framework tab.
Note: |
|---|
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. |
Double-click ComponentCommand.
The connection point appears in the part designer.
In the Properties window, set the Name property of the connection point to urlNotFound.
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" |
Click in Design view to give it the focus.
In the Toolbox, double-click IUIService Dependency.
The service dependency appears in the part designer.
In the Properties window, set the Name property of the service dependency to UIService and select IsRequired.
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.
Note: |
|---|
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" /> |
Click in Design view to give it the focus.
In the ToolBox in the Acropolis Framework tab, double-click SinglePartNavigationManager to add a navigation manager to the design surface.
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" |
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.
The following code shows the completed RssPart.xaml file.
Note: |
|---|
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
In Solution Explorer, expand the RssPart.xaml node and then double-click the code-behind file (RssPart.xaml.vb or RssPart.xaml.cs).
Add the following statements to the top of the file.
|
Imports System.Globalization
Imports Microsoft.Acropolis.Samples.RssEagle.RssContent
|
|
using System.Globalization;
using Microsoft.Acropolis.Samples.RssEagle.RssContent;
|
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.
|
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
|
|
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.");
}
}
|
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.
|
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
|
|
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);
}
|
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.
|
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
|
|
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;
}
}
|
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
In Solution Explorer, double-click FeedPart.xaml.
The FeedPart opens in the part designer.
In the Toolbox, expand the Acropolis Framework tab.
Double-click ComponentCommand to add a command connection point to the design surface.
In the Properties window, set the Name property of the connection point to LoadChannel.
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" |
Click in Design view to give it the focus.
In the Toolbox, double-click IUIService Dependency.
The service dependency appears in the part designer.
In the Properties window, set the Name property of the service dependency to UIService and select IsRequired.
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" /> |
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.
Note: |
|---|
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
In Solution Explorer, expand the FeedPart.xaml node and then double-click the code-behind file (FeedPart.xaml.vb or FeedPart.xaml.cs).
Add the following statements to the top of the file.
|
Imports System.Globalization
Imports Microsoft.Acropolis.Samples.RssEagle.RssContent
Imports System.Collections.Generic
|
|
using System.Globalization;
using Microsoft.Acropolis.Samples.RssEagle.RssContent;
using System.Collections.Generic;
|
Add the following code to the FeedPart class before the constructor.
|
Private Const NumberOfBlogsToDisplay As Integer = 4
Private channel As IRssChannel
Private loadedEntries As New List(Of IRssItem)()
Private rssItemComparer1 As New RssItemComparer()
|
|
const int NumberOfBlogsToDisplay = 4;
IRssChannel channel;
List<IRssItem> loadedEntries = new List<IRssItem>();
RssItemComparer rssItemComparer = new RssItemComparer();
|
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.
|
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
|
|
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);
}
}
|
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.
|
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
|
|
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;
}
|
Select the File | Save All menu option.