火曜日, 2月 26, 2008

WPFとXmlDataProviderにnamespace-uriがくっつくと・・・

WPFのお勉強中 Windows Presentation Foundation データ バインディング: パート 1 を参考にWPFのRSSFeedを見るアプリを作ってみた。 リンク先ではローカルのxmlを参照して更新までやってしまうアプリだったけど、xmlを用意するのが面倒に思ったので、この部ログのフィードを読み込むアプリにしてみた。 最初はXAMLへのデータバインディングをせずにC#側で手動実装のパターンだった。 さすがに記事が2006年だったので、.netFramework3.0の実装で書かれていた。 せっかくvs2008を使ってるんだからということで、LINQtoXMLでコーディングしてみた。 XAML側はリンク先を見てください。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using log4net;
using log4net.Config;

namespace ExampleCS
{
/// <summary>
/// Window1.xaml の相互作用ロジック
/// </summary>
public partial class Window1 : Window
{
   private static readonly ILog log = LogManager.GetLogger(typeof(Window1));

   public Window1()
   {
       // BasicConfigurator replaced with XmlConfigurator.
       XmlConfigurator.Configure(new System.IO.FileInfo(@"log4net.xml"));
       InitializeComponent();
       blog = XDocument.Load(BLOGURL);
   }

   XDocument blog = new XDocument();
   const string BLOGURL = "http://utsuutsu.blogspot.com/feeds/posts/default";

   #region Event
   void Window1_Loaded(object sender, RoutedEventArgs e)
   {
       FillListBox();
   }
   private void entryListBox_Changed(object sender, SelectionChangedEventArgs e)
   {
       if (entryListBox.SelectedIndex != -1)
       {
           XElement element = ((ListBoxItem)entryListBox.SelectedItem).Tag as XElement;
           if (element != null)
           {
               titleText.Text = element.Element("{http://www.w3.org/2005/Atom}title").Value;
               urlText.Text = element.Elements("{http://www.w3.org/2005/Atom}link").
                   Single(el => el.Attribute("rel").Value == "alternate").Attribute("href").Value;
               dateText.Text = element.Element("{http://www.w3.org/2005/Atom}updated").Value;
               bodyText.Text = element.Element("{http://www.w3.org/2005/Atom}content").Value;
           }
       }
   }
   private void updateButton_Click(object sender, RoutedEventArgs e)
   {

   }
   #endregion
   #region private
   void FillListBox()
   {
       entryListBox.Items.Clear();

       var nodes = blog.Descendants("{http://www.w3.org/2005/Atom}entry");
       foreach (XElement element in nodes)
       {
           ListBoxItem item = new ListBoxItem();
           item.Tag = element;
           item.Content = element.Element("{http://www.w3.org/2005/Atom}title").Value;
           entryListBox.Items.Add(item);
       }
   }
   #endregion
}
}
log4netのコードとかもそのままにしてるけど、気にしないでください。 こっちはこれまでの学習の効果があってすぐにできた。 問題はXAMLだけを利用する(バインディングですべて解決)パターンだった。 XmlDataProviderでフィードを読ませてバインディングしていけばいいのかと思いきや、namespace-uriを指定する方法がわからない。。。 なんとなくクラスの関連上XmlNamespaceManagerがそれに該当するんだろうということはわかったんだけど、これってクラス->オブジェクトなんでXAMLに直にuriを書くわけにはいかず。。。(stringになるもんね) どうすりゃいいのかわからず、 あっちこっち探し回っているとMSDNのフォーラムに対応方法が載っていた。 Resources上でXmlNamespaceMappingCollectionにKeyを付けてXmlNamespaceMappingを生成するというやり方で、Key名を使ってXmlDataProviderにバインディングすることができるらしい。 で、やってみた結果がこれ(今度はXAMLだけで逆にC#側のコーディングは0です。)
<Window x:Class="ExampleCS.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ExsampleCS">
<StackPanel>
    <StackPanel.Resources>
        <XmlNamespaceMappingCollection x:Key="mapping">
            <XmlNamespaceMapping Uri="http://www.w3.org/2005/Atom" Prefix="rss"/>
        </XmlNamespaceMappingCollection>
        <XmlDataProvider x:Key="RssFeed" XmlNamespaceManager="{StaticResource mapping}" Source="http://utsuutsu.blogspot.com/feeds/posts/default" />
    </StackPanel.Resources>
    <TextBlock HorizontalAlignment="Center" FontWeight="Bold">
        BlogEditor
    </TextBlock>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <ListBox Name="entryListBox" Height="300" ItemsSource="{Binding Source={StaticResource RssFeed}, XPath=//rss:entry}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding XPath=rss:title}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Grid Width="500" Margin="5" DataContext="{Binding ElementName=entryListBox, Path=SelectedItem}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="25"/>
                <RowDefinition Height="25"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0">Title:</TextBlock>
            <TextBox Binding.XmlNamespaceManager="{StaticResource mapping}" Grid.Row="0" Grid.Column="1" Name="titleText" Text="{Binding XPath=rss:title}" />
            <TextBlock Grid.Row="1" Grid.Column="0">Url:</TextBlock>
            <TextBox Binding.XmlNamespaceManager="{StaticResource mapping}" Grid.Row="1" Grid.Column="1" Name="urlText" Text="{Binding XPath=rss:link/@href[1]}"/>
            <TextBlock Grid.Row="2" Grid.Column="0">Date:</TextBlock>
            <TextBox Binding.XmlNamespaceManager="{StaticResource mapping}" Grid.Row="2" Grid.Column="1" Name="dateText" Text="{Binding XPath=rss:updated}"/>
            <TextBlock Grid.Row="3" Grid.Column="0">Body:</TextBlock>
            <TextBox Binding.XmlNamespaceManager="{StaticResource mapping}" Grid.Row="3" Grid.Column="1" Name="bodyText" TextWrapping="Wrap" Text="{Binding XPath=rss:content}"/>
        </Grid>
    </StackPanel>
</StackPanel>
</Window>
XmlDataProviderにバインディングしてListBoxにバインディングするところまでは問題なかったんだけど、最後にイベント連動のGrid側のDataContextの内容がTextBox上で取得できないという状態になり、 またまた悪戦苦闘。。。 やっぱり、namespace-uriがらみだった。。。 TextBoxにBinding.XmlNamespaceManager="{StaticResource hoge}"って実装が必要なんだそうだ。。。 まあ、最終的にはこんなのができた。 ExampleCS Application側(app.xaml)でスタイルをあれこれすればもうちょい見た目もよくなるだろう。。。 オンラインセミナーで「Expression Blend」とかいうツールの紹介がされていた。デザインに関してはこっちでやるといいよ~とMSの人は言っていた。 また、機会があればそっちも見てみようと思う。
ひとつだけ腑に落ちない部分がある。 それは、XAML上だけで表現するときにXPathで属性の条件付き取得の式がちゃんと評価されてないんじゃないの?って思うところだ。
rss:link[@ref='hoge']/@href
これで、linkタグの中でref属性がhogeのhref属性値が取得できると思うんだけど、何か違うのかな?? LINQでSingleを使った時には問題なかったんだけど。。。(Singleは1つ以外だとExceptionが発生する)

0 件のコメント:

failed to read qemu headerのときのメモ

かなり久々。。。 忘れないようにここに書きこんでおく。 ちょっとした手違いで libvirtでイメージを起動しようとすると failed to read qemu header なんておっしゃられて起動しない。。。 vmwareserverを使って...