Condividi tramite


XAML Load support in Cider May CTP Release. Sample app.

You would be glad to know that some of the improvements in May CTP include:

- Support for x:Static

- Traversing properties, for example:

TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(Offset)"

- Improved resolution of Control Templates, Bindings, Resources and Styles

- Improved error reporting for XAML files that contain errors

- Basic usage of Storyboard

Parts of the above features are demonstrated in the following sample. I chose to create a simple single-feed RSS reader with only XAML (no code behind).

Our XAML Load Support is not yet complete. Should you encounter XAML related issues, please report them here: https://connect.microsoft.com/default.aspx

Your feedback is valuable and appreciated.

To ensure that you report unknown issues only, please check our list of known May CTP limitations here: https://channel9.msdn.com/wiki/default.aspx/Cider.MayCTPXAMLLoadLimitations

Let’s go ahead and do the sample application.

Task 1: How will it look?

I decided to make it look something like this:

[RSS News Title 1]

**************

News data1

**************

[RSS News Title 2]

**************

News data2

**************

Task 2: Find RSS feed

Let’s take the MSNBC entertainment feed: https://rss.msnbc.msn.com/id/3032083/device/rss/rss.xml

Of course you can choose any feed you want.

If you open the link you can see the RSS feed contains the following:

  • <channel> named “MSNBC.com: Entertainment” (on top)

  • a collection of items in the following format:

<item>

<title>Best and worst of summer reality TV</title>

<link>https://www.msnbc.msn.com/id/12372875/</link>

</item>

I decided that <title> and <link> are the things we’ll be using, you can choose to display other information as well.

Our application would now look like:

[insert item.title here]

***********************

<Display the site that item.link points to here>

***********************

[insert the next item.title here]

***********************

<Display the site that the next item.link points to here>

***********************

Can you imagine it? I almost can.

Task 3: Now we pretty much know what we want, let’s take a look at the final sample application and break it down into pieces in a moment.

Now is the time to click here to take a look at the sample if you haven't done it already!

First let’s try to get the XML from the feed and display some part of it.

  1. Start with a Window that has an empty Grid, and add the XMLDataProvider:

<Window.Resources>

<XmlDataProvider x:Key="RssData" Source="https://rss.msnbc.msn.com/id/3032083/device/rss/rss.xml" XPath="rss/channel" />

</Window.Resources>

<Grid >

</Grid>

At this point we expect the XmlDataProvider to provide us with XML, let’s see if it really does.

Note that we specified that the provider will get the XML starting at the rss/channel node (that’s where we saw the <item>-s are).

  1. Try using the provided XML data (note that the updates are in bold)

<Grid>

< TextBlockText = "{Binding Source={StaticResource RssData}, XPath=item}"TextWrapping="Wrap" />

</Grid>

We specified a block of text that is using the RssData as source and picks up a single item from it.

Switching to the designer shows us this picture:

Seems like its working. Great! Let’s continue with this:

 

  <Grid>

    <TextBlock Text="{Binding Source={StaticResource RssData}, XPath=item/title}" TextWrapping="Wrap" />

  </Grid>

Pretty much what we wanted.

It seems that the XML data provider is working. Now you can go on and do some experiments or you can stay with me for the next step which is:

 

  <Grid>

    < ListBoxItemsSource = "{Binding Source={StaticResource RssData}, XPath=item/title}" />

  </Grid>

We replaced the textbox with listbox.

We got all the RSS Feed titles. Could we do better with 2 lines of XAML?

We know how to use the RSS data to “feed” our listbox. Now let’s focus on the eye candy part.

A ListBox is usually defined like the one below:

 

  <Window.Resources>

    <XmlDataProvider x:Key="RssData" Source="https://rss.msnbc.msn.com/id/3032083/device/rss/rss.xml" XPath="rss/channel" />

  </Window.Resources>

  <Grid>

    < Grid.RowDefinitions >

      < RowDefinitionHeight = "Auto" />

      < RowDefinitionHeight = "Auto" />

    </ Grid.RowDefinitions >

   

    <ListBox Grid.Row = "0" ItemsSource="{Binding Source={StaticResource RssData}, XPath=item/title}" />

    < ListBoxGrid.Row = "1">

      < ListBoxItemContent = "Item 1" />

      < ListBoxItemContent = "Item 2" />

      < ListBoxItemContent = "Item 3" />

    </ ListBox >

  </Grid>

Each of the ListBoxItem-s can be customized to look the way we want.

To do this we’ll use ControlTemplate:

 

  <Window.Resources>

    <XmlDataProvider x:Key="RssData" Source="https://rss.msnbc.msn.com/id/3032083/device/rss/rss.xml" XPath="rss/channel" />

    < StyleTargetType = "{x:Type ListBoxItem}"x:Key="RSSFeedItem">

      < SetterProperty = "Template">

        < Setter.Value >

          < ControlTemplateTargetType = "{x:Type ListBoxItem}">

            < TextBlockText = "{TemplateBinding Content}"FontSize="16pt" />

          </ ControlTemplate >

        </ Setter.Value >

      </ Setter >

    </ Style >

  </Window.Resources>

  <Grid>

    <Grid.RowDefinitions>

      <RowDefinition Height="Auto" />

      <RowDefinition Height="Auto" />

    </Grid.RowDefinitions>

   

    <ListBox Grid.Row="0" ItemsSource="{Binding Source={StaticResource RssData}, XPath=item/title}" />

    <ListBox Grid.Row="1" ItemContainerStyle="{StaticResource RSSFeedItem}" >

      <ListBoxItem Content="Item 1" />

      <ListBoxItem Content="Item 2" />

      <ListBoxItem Content="Item 3" />

    </ListBox>

  </Grid>

Above we setup a style that sets a template for a ListBoxItem. Then we specified this style to be used in our ListBox.

One interesting thing is the Text = "{TemplateBinding Content}". It’s the same as saying: “put in the Text whatever is in the ListBoxItem.Content”.

We got this:

Let’s try to get something like this:

<ListBox Grid.Row="0" ItemsSource="{Binding Source={StaticResource RssData}, XPath=item}" ItemContainerStyle="{StaticResource RSSFeedItem}" >/>

Above we changed the Xpath so that the ListBoxItems will get all the item information (not the item/title as before) and we added our styling to the ListBox.

Now, let’s restyle the listbox items to separate the Title and the Link of the news:

  <Window.Resources>

    <XmlDataProvider x:Key="RssData" Source="https://rss.msnbc.msn.com/id/3032083/device/rss/rss.xml" XPath="rss/channel" />

    <Style TargetType="{x:Type ListBoxItem}" x:Key="RSSFeedItem">

      <Setter Property="Template">

        <Setter.Value>

    <ControlTemplate TargetType="{x:Type ListBoxItem}">

            < StackPanel >

              < TextBlockText = "{Binding XPath=title}"FontSize="10pt"FontWeight="SemiBold" />

              < TextBlockText = "{Binding XPath=link}"FontSize="10pt" />

      </ StackPanel >

          </ControlTemplate>

        </Setter.Value>

      </Setter>

    </Style>

  </Window.Resources>

  <Grid>

    <Grid.RowDefinitions>

      <RowDefinition Height="Auto" />

      <RowDefinition Height="Auto" />

    </Grid.RowDefinitions>

   

    <ListBox Grid.Row="0" ItemsSource="{Binding Source={StaticResource RssData}, XPath=item}"

             ItemContainerStyle="{StaticResource RSSFeedItem}"/>

  </Grid>

The changes we made are –

  1. We replaced TemplateBinding with Binding XPath, because we wanted to bind to the <item> in the RSS feed instead of binding to a property of ListBoxItem.

  2. We replaced XPath=item/title with XPath=item in the definition of the ListBox items source.

We may want instead of displaying the link to display what is in the link. We may want to have an expand and collapse behavior for each of the items.

          <ControlTemplate TargetType="{x:Type ListBoxItem}">

            < Grid >

              < ExpanderHeader = "{Binding XPath=title}">

                  < FrameSource = "{Binding XPath=link}" />

              </ Expander >

            </ Grid >

          </ControlTemplate>

Above we replace the two TextBlocks and the StackPanel with a Grid, Expander and Frame. Here’s what we got:

I guess you already built and ran the above. Seems OK, but the frame is not quite what we want (some people may want it like this). Otherwise we’ve got our reader in a [kind of] usable state.

Congratulations!!!

We can now prettify our solution.

To be continued…

Continuing. Did you really think we’d stop here?

We can:

  1. Modify the Expander and/or Frame so that the frame is bigger
  2. Make the Expander look nicer

Of course you can do a lot of other stuff and make it even better – would be very nice to see what you did.

We’ll go fast forward from now on.

If you read up to here you already know how to use ControlTemplates, Styles and DataBinding, as well as using the XML Data Provider to get XML from somewhere. Well done!

I created a template for the Expander:

<ControlTemplate x:Key="AnimatedExpander" TargetType="{x:Type Expander}">

</ControlTemplate>

You can find the template code in the final example solution.

I’ll briefly explain the things that interested me.

 

                <ControlTemplate.Triggers>

                  <EventTrigger RoutedEvent="Mouse.MouseEnter">

                    <EventTrigger.Actions>

                      <BeginStoryboard Storyboard="{StaticResource AniEnter}"/>

                    </EventTrigger.Actions>

                  </EventTrigger>

                  <Trigger Property="IsChecked" Value="true">

                    <Setter Property="Data" Value="M 7,9 L 10,6 L 13,9 M 7,13 L 10,10 L 13 13" TargetName="arrow" />

                  </Trigger>

                </ControlTemplate.Triggers>

The above code is part of the ControlTemplate for the Expander. You can see that it has two types of triggers. The EventTrigger is probably obvious. The IsChecked trigger is more interesting -> it will change the arrow of the expander to “collapse arrow” if the Expander is expanded and will revert it back to “up arrow” when the Expander is collapsed. Get it? The first trigger will occur each time an event occurs, while the second one will change a value and revert it back based on a condition.

Animations

 

                  <Storyboard x:Key="AniEnter">

                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="AniControl"

                      Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(Offset)">

         <DoubleAnimationUsingKeyFrames.KeyFrames>

                        <DoubleKeyFrameCollection>

                          <SplineDoubleKeyFrame KeySpline="0.5,0.5,0.5,0.5" Value="0.1" KeyTime="00:00:00.1000000" />

                          <SplineDoubleKeyFrame KeySpline="0.5,0.5,0.5,0.5" Value="0.4" KeyTime="00:00:00.7000000" />

                        </DoubleKeyFrameCollection>

                      </DoubleAnimationUsingKeyFrames.KeyFrames>

                    </DoubleAnimationUsingKeyFrames>

                  </Storyboard>

What we express in the XAML above is:

  1. Find the control named AniControl

  2. Find the property: "(Control.Background).(LinearGradientBrush.GradientStops)[1].(Offset)"

This will be the property that will get its value changed (the animated property). We could as well say “Control.Width” here if the control was Button for example.

If we change the Width property of a Button from 100 to 200 in 5 seconds and then back to 100 do we get animation? We get a “squishing” button. Is this cool or not?

You already know what the above animation is supposed to do: it is supposed to change some Offset of a gradient somewhere from 0.1 to 0.4 in about 0.6 seconds.

I should mention the purpose of the KeySpline here:

 

<SplineDoubleKeyFrame KeySpline="0.5,0.5,0.5,0.5" Value="0.4" KeyTime="00:00:00.7000000" />

But I won’t. Feel free to try different values like KeySpline="0.2,0.2,0.9,0.6".

                  <EventTrigger RoutedEvent="Mouse.MouseEnter">

                    <EventTrigger.Actions>

   <BeginStoryboard Storyboard="{StaticResource AniEnter}"/>

                    </EventTrigger.Actions>

                  </EventTrigger>

 

The above code says “when MouseEnter start the animation”.

Another thing you might find interesting is (this code is not part of the sample):

        <Button>

          <Button.Background>

            <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">

              <LinearGradientBrush.GradientStops>

                <GradientStop Offset="0.0" Color="#100000FF" />

                <GradientStop Offset="0.5" Color="#200000FF" />

                <GradientStop Offset="1" Color="#400000FF" />

              </LinearGradientBrush.GradientStops>

            </LinearGradientBrush>

          </Button.Background>

          Click me, click me now

        </Button>

All I want to say about LinearGradientBrush-es: they seem to work really well with Opacity less than 1.

Bye-bye!

MayCTPExample1RSSReader.zip

Comments

  • Anonymous
    June 21, 2006
    Great to see you blog finally!

  • Anonymous
    June 29, 2006
    Wanted to point you to an article that one of our SDETs, Nikola Mihaylov,&amp;nbsp;wrote -- it's an RSS reader...

  • Anonymous
    November 27, 2008
    Hi send me how to populate a label control dynamically using itemtemplate. In my requirment i am dynamically assigning the value to the labels... but the requirment asks needs to populate and assign the value based on how many values come.. it will come upto 36 values... can u please provide a POC to check that account please send me the mail tashok_infotech@yahoo.co.in

  • Anonymous
    November 27, 2008
    Hi, I tried the scenario "add the number for each item next to ListBox", and it works - hopefully this is similar to the thing you want to do. Here's how to do it:

  1. Download the sample from: http://msdn.microsoft.com/en-us/library/ms771560.aspx
  2. in data.cs, add this to the Person class:      private ObservableCollection<Person> container;      public int Index {          get { return container.IndexOf(this) + 1; }      }      public Person(string first, string last, string town, ObservableCollection<Person> container) {          this.container = container;          this.firstname = first;          this.lastname = last;          this.hometown = town;      }
  3. change the People class to use the new constructor:    public class People : ObservableCollection<Person>    {        public People(): base()        {            Add(new Person("Michael", "Alexander", "Bellevue", this));            Add(new Person("Jeff", "Hay", "Redmond", this));            Add(new Person("Christina", "Lee", "Kirkland", this));            Add(new Person("Samantha", "Smith", "Seattle", this));        }    }
  4. in Window1.xaml, add DataTemplate in Window.Resources (for the Person class):        <DataTemplate x:Key="person" >            <StackPanel Orientation="Horizontal">                <Label Content="{Binding Index}" />                <Label Content="{Binding FirstName}" />            </StackPanel>        </DataTemplate>
  5. Add ItemTemplate="{StaticResource person}" to the ListBox Note: This implementation is missing one part: you may have to call property updated for each item whenever a new item is added/removed from the People observable collection Hope this helps! Please let me know! Nikola