none
存在しないPathが設定されている場合のConverterの挙動について RRS feed

  • 質問

  • DataContextとしてLinq to XMLのXElementを指定している場合に、

    IsChecked={Binding Path=Attribute[check].Value, Converter={StaticResource myConverter}}

    のような定義をしているとします。

    DataContextはコードでセットしているのですが、
    このとき、最初にConverterが呼ばれるタイミングで @check が存在していないと、その後バインディング元のXElementに @check を作成してもConverterは呼ばれません。

    バインディング元の更新に伴って再びConverterが呼ばれるようにはできないのでしょうか。

    ちなみに、バインディング方法として、XAML上で直接ObjectDataProvidorを定義してXMLファイルをLoadしている場合は、最初に @check がなくてもバインディング元を更新すれば再びConverterが機能するようになります。

    また、そもそもPathが存在しない場合にはConverterが呼ばれないようなですが、存在しない場合の処理を定義することはできないのでしょうか。

    これが可能であれば、存在しない場合に属性を作成する、あるいは、Defaultの値をセットするということをしたいのですが。

     

    よろしくお願いいたします。

    2010年12月30日 9:54

回答

  • よく読んではないのですが、こういうデータ構造を扱う場合はXElementを直接使うよりも自分でクラス定義したほうがいいように思います。
    そうすれば、カスタムロジックを入れることができるので、思うような挙動を入れることができると思います。
    かずき Blog:http://d.hatena.ne.jp/okazuki/
    • 回答としてマーク 山本春海 2011年1月28日 5:33
    2011年1月14日 0:46

すべての返信

  • WPF はまだ勉強中なため、変なこと書いていましたらすみません。
    以下のコードで試してみましたが、button1 をクリックするとコンバーターは呼ばれ、チェック状態に反映されました。

    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <Grid.Resources>
                <local:MyConverter x:Key="myConverter" />
            </Grid.Resources>
            <CheckBox Height="16" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top"
                      IsChecked="{Binding Path=Attribute[check].Value, Converter={StaticResource myConverter}}" HorizontalAlignment="Left" Width="120">
                CheckBox
            </CheckBox>
            <Button Height="23" Margin="12,43,0,0" Name="button1" VerticalAlignment="Top" Click="button1_Click" HorizontalAlignment="Left" Width="76">Button</Button>
        </Grid>
    </Window>

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            var test = new XElement("test");
            //test.SetAttributeValue("check", "Y");
            this.DataContext = test;
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            ((XElement)this.DataContext).SetAttributeValue("check", "Y");
        }
    }

    public class MyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return object.Equals(value, "Y");
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return (bool)value ? "Y" : "N";
        }
    }

    > 最初にConverterが呼ばれるタイミングで @check が存在していないと

    と書かれましたが、@check が存在しないと Converter が呼ばれることはありませんでしたので、書かれたことが再現できませんでした。

    > また、そもそもPathが存在しない場合にはConverterが呼ばれないようなですが、存在しない場合の処理を定義することはできないのでしょうか。

    Path が存在しない、というのは @check が存在しないということですね?
    このような場合にも Converter が呼び出されるようにする方法は解らなかったのですが、Binding の FallbackValue プロパティが使えそうに思いました。

    IsChecked="{Binding Path=Attribute[check].Value, Converter={StaticResource myConverter}, FallbackValue=True}"

    • 編集済み TH01 2011年1月14日 0:44 訂正:ConvertBack の中身が間違って T or F になっていたので修正
    2011年1月5日 6:54
  • ご回答いただきましてありがとうございました。

    FallbackValueプロパティはさっそく使ってみます。

     

    >> 最初にConverterが呼ばれるタイミングで @check が存在していないと

    >と書かれましたが、@check が存在しないと Converter が呼ばれることはありませんでしたので、書かれたことが再現できませんでした。

    書き方が良くなかったですね。すみません。

    おっしゃる通り、@checkが存在しないとConverterが呼ばれることはありませんでした。

    私が言いたかったのは、もしも@checkが存在していたらConverterが呼ばれるタイミングのことで、そのときに@checkが存在していないと、その後@checkを作成しても二度とConverterが呼ばれない、ということでした。

    …伝わりますでしょうか。

    現象が発生していたのはCheckBox付きのTreeViewにXMLデータをバインディングしているときでして、チェックをつけたらその子孫や先祖のチェックの状態を変更するために@checkの値を更新するということを行っていました。

    初期状態ではXMLデータに@checkは存在していません。

    こういった状態で、TreeViewを展開してからチェックをつけると、子孫のチェックの状態は変わらず、展開せずにチェックをつけてから展開すると子孫のチェックの状態が反映されているという現象が発生していたのです。

    TreeViewの場合、展開して初めてConverterが呼ばれていたのと、初期状態で@checkが存在している場合は正常に動作したことの2点から、原因が存在しないPathにあるのでは思ったわけです。

    しかしながら、ご教授いただきましたコードで試してみたところ、最初に@checkが存在していなくても、@checkが出来上がった段階で正常にConverterが呼ばれていましたので、原因は別にあったようです。

    TreeViewに原因があるのか、と思いなおし、次のようにコードを変更して試してみましたが、これも全く問題なく動作してしまいました。

    <tests>
     <test value="1">
      <test value="2/>
     </test>
     <test value="3">
      <test value="4"/>
     </test>
    </tests>

    という内容のXMLファイルを読み込み、

    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <Grid.Resources>
                <local:MyConverter x:Key="myConverter" />
            </Grid.Resources>
        <TreeView ItemsSource="{Binding Path=Elements}">
                <HierarchicalDataTemplate ItemsSource="{Binding Path=Elements}">
                    <CheckBox Height="16" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top"
                      IsChecked="{Binding Path=Attribute[check].Value, Converter={StaticResource myConverter}}" HorizontalAlignment="Left" Width="120">
                CheckBox
                    </CheckBox>
               </HierarchicalDataTemplate>
            </TreeView>
            <Button Height="23" Margin="12,43,0,0" Name="button1" VerticalAlignment="Top" Click="button1_Click" HorizontalAlignment="Left" Width="76">Button</Button>
        </Grid>
    </Window>

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            XDocument xdoc = XDocument.Load("パス");
            this.DataContext = xdoc.Root:
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            IEnumerable<XElement> de = from el in ((XElement)this.DataContext).DescendantsAndSelf()
                 select el;
            foreach(XElement el in de)
                 el.SetAttributeValue("check", "Y");
        }
    }

    状況を整理して原因を追究してみたいと思います。

     

    ありがとうございました。

    2011年1月13日 9:41
  • よく読んではないのですが、こういうデータ構造を扱う場合はXElementを直接使うよりも自分でクラス定義したほうがいいように思います。
    そうすれば、カスタムロジックを入れることができるので、思うような挙動を入れることができると思います。
    かずき Blog:http://d.hatena.ne.jp/okazuki/
    • 回答としてマーク 山本春海 2011年1月28日 5:33
    2011年1月14日 0:46
  • 回答ありがとうございます。

    クラス定義も検討してみたいと思います。

    ただ、動作が気持ち悪いので原因が何なのか追求してみようと思います。

    2011年1月14日 6:47