none
IValueConverterクラスで表示する画像の切り替えについて RRS feed

  • 質問

  • こんにちは。
    表題のとおり、「IValueConverterクラスで表示する画像の切り替えについて」教えてほしいです。

    コンバータークラスで、整数型のデータから画像を表示するプログラムを作成しました。
    この整数値は、比較される整数値が更新されたときに表示する画像を変更したいと考えています。
    具体的には、以下のようになっています。

    	public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                var image = null as BitmapImage;
    
                var record = value as SampleRecord;
                if (record == null) return null;
    
                if (SelectedNoManager.Instance.SelectedNo == record.No)
                {
                    image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/arrow.png", UriKind.Absolute));
                }
                else
                {
                    image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/wait.png", UriKind.Absolute));
                }
    
                return image;
            }

    切り替えするnoの値は、ユーザーの操作によっていくつかのウィンドウで再設定される可能性がありました。
    そのためSingletonなクラスで保持することにしました。

    private static readonly SelectedNoManager _Instance = new SelectedNoManager();
    
            /// <summary>
            /// インスタンスを取得します。
            /// </summary>
            public static SelectedNoManager Instance
            {
                get { return _Instance; }
            }
    
            private int _SelectedNo = 1;
    
            /// <summary>
            /// 選択している番号を取得または設定します。
            /// </summary>
            public int SelectedNo
            {
                get { return _SelectedNo; }
                set
                {
                    _SelectedNo = value;
                    RaisePropertyChanged(this, "SelectedNo");
                }
            }

    サンプルではウィンドウのボタンを押下して、SelectedNoを1から2に切り替えています。
    しかし、ウィンドウの画像が更新されません。こういうときはどのようにして切り替えすることができるのでしょうか。


    サンプルのプログラム
    http://1drv.ms/1x2xG3N

    以上です。わかるかたいましたら、教えてほしいです。よろしくお願いします。

    以下メインウィンドウのコード

    public event PropertyChangedEventHandler PropertyChanged;
    
    
            private ObservableCollection<SampleRecord> _SampleRecords = new ObservableCollection<SampleRecord>
            {
                new SampleRecord{ No = 1, Name = "太郎A", Args = "おまけ1" },
                new SampleRecord{ No = 2, Name = "太郎B", Args = "おまけ2" },
                new SampleRecord{ No = 3, Name = "太郎C", Args = "おまけ3" },
                new SampleRecord{ No = 4, Name = "太郎D", Args = "おまけ4" },
                new SampleRecord{ No = 5, Name = "太郎E", Args = "おまけ5" },
                new SampleRecord{ No = 6, Name = "太郎F", Args = "おまけ6" },
                new SampleRecord{ No = 7, Name = "太郎G", Args = "おまけ7" },
                new SampleRecord{ No = 8, Name = "太郎H", Args = "おまけ8" },
            };
    
    
            /// <summary>
            /// サンプルで作成したデータグリッドのデータコレクションを取得または設定します。
            /// </summary>
            public ObservableCollection<SampleRecord> SampleRecords
            {
                get { return _SampleRecords; }
                set
                {
                    _SampleRecords = value;
                    RaisePropertyChanged(this, "SampleRecords");
                }
            }
    
    
            public MainWindow()
            {
                InitializeComponent();
    
                // init
                this.DataContext = this;
            }
    
    
            /// <summary>
            /// <seealso cref="System.ComponentModel.INotifyPropertyChanged.PropertyChanged"/> イベントを発生させます。
            /// </summary>
            /// <param name="sender">イベントを発生させる元になるオブジェクト。</param>
            /// <param name="propertyNames">変化が発生したプロパティ名のテキスト。</param>
            public void RaisePropertyChanged(object sender, params string[] propertyNames)
            {
                if (PropertyChanged == null) return;
    
                foreach (var name in propertyNames)
                {
                    PropertyChanged(sender, new PropertyChangedEventArgs(name));
                }
            }
    
    
            /// <summary>
            /// ボタンを押下したとき、選択しているNoを変更するためのイベントです。
            /// </summary>
            /// <param name="sender">未使用。</param>
            /// <param name="e">未使用。</param>
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                SelectedNoManager.Instance.SelectedNo = 2;
                RaisePropertyChanged(this, "SampleRecords"); // 更新されない
            }


    • 編集済み ichiethel 2015年3月19日 3:00
    2015年3月19日 2:59

回答

  • 少し補足をしておきます。

    サンプルの内容を試してみたところ、上手く動作していることが確認できました。

    IMultiValueConverterは使ったことがありませんでした。

    今回のケースでは、その通りで、IMultiValueConverterを使うのが適切だと思います。
    しかし、IMultiValueConverterを使わなくても、最初に掲載されていたConverterのままでも実現可能です。 質問に掲載されたコード、およびダウンロードした訂正済みのコードを拝見させていただきましたが、バインディングの理解が不足しているように思われます。
    それは、ObservableCollectionは元々、INotifyPropertyChangedを実装しており、ご自分でINotifyPropertyChangedを実装する必要はないところからも推し量ることができます。
    なぜ、ご質問のコードでは画像が移動せず、思ったように動作しなかったのでしょうか?
    それはObservableCollection、INotifyPropertyChangedといったバインドの仕組みをきちんと把握されていないからだと思います。把握していれば、IMultiValueConverterが如何に適切であるかが理解できると思います。

    コンバーターとはバインド時、およびその再評価時に実行されるものです。画像の列がバインドしているのはObservableCollectionではなく、そのそれぞれの要素です。ですから、その要素を再評価しなければコンバーターは実行されません。つまり、SampleRecordクラスにこそ、INotifyPropertyChangedが必要なのです。
    しかし、今回はバインドしているプロパティは一切変化しません。つまり、SampleRecordクラスのプロパティの変更通知は要りません。画像を切り替えるのはSampleRecordクラス以外の値の変化が起点になります。そこで、IMultiValueConverterを用いて、SampleRecordクラス以外の値の変化を察知するようにしているわけです。

    #時間がある時にでも、SampleRecordクラスにINotifyPropertyChangedを実装して試してみて下さい。このパターンはObservableCollectionを使う際の王道ですので、覚えておいて下さい。
    開発を急がれていれば仕方ありませんが、自分の思うように動かなかったところを理解して先に進むことは、とても大切なことです。動かないコードをそのまま葬るのではなく、そういうコードから学ぶことはたくさんあるはずです。

    (追記)ポイントになりそうなところのコードを載せておきます。

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var image = null as BitmapImage;
    
        //var record = value as SampleRecord;
        var record = value as int?;
    
        if (record == null) return null;
    
        //if (SelectedNoManager.Instance.SelectedNo == record.No)
        if (SelectedNoManager.Instance.SelectedNo == record)
        {
            image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/arrow.png", UriKind.Absolute));
        }
        else
        {
            image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/wait.png", UriKind.Absolute));
        }
    
        return image;
    }
    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        SelectedNoManager.Instance.SelectedNo = 2;
    
     //   RaisePropertyChanged(this, "SampleRecords"); // 更新されない
    
        foreach (var item in _SampleRecords)
        {
            item.RaisePropertyChanged(item, "No");
        }
    }
     <Image Height="15" Source="{Binding No,Converter={StaticResource NoToImage}}" />
    #SampleRecordクラスにINotifyPropertyChangedを実装したコードは略


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年3月19日 6:19
    モデレータ
  • こんにちは。

    とりあえずは、
    MultiBindingを使って変更通知を受けるたびにコンバータを動かせば良いのではないでしょうか。

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <!--<Image Source="{Binding Converter={StaticResource NoToImage}}" Height="15" />-->
            <!--mod start-->
            <Image Height="15">
                <Image.Source>
                    <MultiBinding Converter="{StaticResource NoToImage}">
                        <MultiBinding.Bindings>
                            <Binding />
                            <Binding Source="{x:Static mana:SelectedNoManager.Instance}" Path="SelectedNo" />
                        </MultiBinding.Bindings>
                    </MultiBinding>
                </Image.Source>
            </Image>
            <!--mod end-->
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

    public class NoToImage : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var image = null as BitmapImage;
    
            var record = values[0] as SampleRecord;
            if (record == null) return null;
    
            var selectedNo = (int)values[1];
    
            if (selectedNo == record.No)
            {
                image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/arrow.png", UriKind.Absolute));
            }
            else
            {
                image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/wait.png", UriKind.Absolute));
            }
    
            return image;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    ※エラー処理などはしてないです





    2015年3月19日 3:31
    モデレータ

すべての返信

  • こんにちは。

    とりあえずは、
    MultiBindingを使って変更通知を受けるたびにコンバータを動かせば良いのではないでしょうか。

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <!--<Image Source="{Binding Converter={StaticResource NoToImage}}" Height="15" />-->
            <!--mod start-->
            <Image Height="15">
                <Image.Source>
                    <MultiBinding Converter="{StaticResource NoToImage}">
                        <MultiBinding.Bindings>
                            <Binding />
                            <Binding Source="{x:Static mana:SelectedNoManager.Instance}" Path="SelectedNo" />
                        </MultiBinding.Bindings>
                    </MultiBinding>
                </Image.Source>
            </Image>
            <!--mod end-->
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

    public class NoToImage : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var image = null as BitmapImage;
    
            var record = values[0] as SampleRecord;
            if (record == null) return null;
    
            var selectedNo = (int)values[1];
    
            if (selectedNo == record.No)
            {
                image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/arrow.png", UriKind.Absolute));
            }
            else
            {
                image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/wait.png", UriKind.Absolute));
            }
    
            return image;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    ※エラー処理などはしてないです





    2015年3月19日 3:31
    モデレータ
  • Tak1waさん

    ご返信ありがとうございます。
    サンプルの内容を試してみたところ、上手く動作していることが確認できました。

    IMultiValueConverterは使ったことがありませんでした。

    ConverterのParameterが使いづらく困っていたのですが、MultiにすることでBindingの変更通知用プロパティを増やすのですね。こういう考え方もあるのか、と感心しました。

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

    2015年3月19日 4:15
  • >しかし、ウィンドウの画像が更新されません。こういうときはどのようにして切り替えすることができるのでしょうか。

    特に掲載されたコードに問題があるように思えません。こちらの環境(VS2013、 .NET Framework 4.5、Windows 8.1)で試してみましたが、問題なく画像が1から2に移りました。
    どのような環境でテストされているのでしょうか?

    #それとも私がご質問の意味を取り違えているのでしょうか?

    #(追記)
    あ~、アップされているコードが既に動作する版に差し替えられているのですね。掲載されたコードはアップされているコードと同じという前提でいました。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/


    • 編集済み trapemiyaModerator 2015年3月19日 5:05 追記
    • 回答としてマーク ichiethel 2015年4月3日 6:01
    • 回答としてマークされていない ichiethel 2015年4月3日 6:01
    2015年3月19日 4:59
    モデレータ
  • 少し補足をしておきます。

    サンプルの内容を試してみたところ、上手く動作していることが確認できました。

    IMultiValueConverterは使ったことがありませんでした。

    今回のケースでは、その通りで、IMultiValueConverterを使うのが適切だと思います。
    しかし、IMultiValueConverterを使わなくても、最初に掲載されていたConverterのままでも実現可能です。 質問に掲載されたコード、およびダウンロードした訂正済みのコードを拝見させていただきましたが、バインディングの理解が不足しているように思われます。
    それは、ObservableCollectionは元々、INotifyPropertyChangedを実装しており、ご自分でINotifyPropertyChangedを実装する必要はないところからも推し量ることができます。
    なぜ、ご質問のコードでは画像が移動せず、思ったように動作しなかったのでしょうか?
    それはObservableCollection、INotifyPropertyChangedといったバインドの仕組みをきちんと把握されていないからだと思います。把握していれば、IMultiValueConverterが如何に適切であるかが理解できると思います。

    コンバーターとはバインド時、およびその再評価時に実行されるものです。画像の列がバインドしているのはObservableCollectionではなく、そのそれぞれの要素です。ですから、その要素を再評価しなければコンバーターは実行されません。つまり、SampleRecordクラスにこそ、INotifyPropertyChangedが必要なのです。
    しかし、今回はバインドしているプロパティは一切変化しません。つまり、SampleRecordクラスのプロパティの変更通知は要りません。画像を切り替えるのはSampleRecordクラス以外の値の変化が起点になります。そこで、IMultiValueConverterを用いて、SampleRecordクラス以外の値の変化を察知するようにしているわけです。

    #時間がある時にでも、SampleRecordクラスにINotifyPropertyChangedを実装して試してみて下さい。このパターンはObservableCollectionを使う際の王道ですので、覚えておいて下さい。
    開発を急がれていれば仕方ありませんが、自分の思うように動かなかったところを理解して先に進むことは、とても大切なことです。動かないコードをそのまま葬るのではなく、そういうコードから学ぶことはたくさんあるはずです。

    (追記)ポイントになりそうなところのコードを載せておきます。

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var image = null as BitmapImage;
    
        //var record = value as SampleRecord;
        var record = value as int?;
    
        if (record == null) return null;
    
        //if (SelectedNoManager.Instance.SelectedNo == record.No)
        if (SelectedNoManager.Instance.SelectedNo == record)
        {
            image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/arrow.png", UriKind.Absolute));
        }
        else
        {
            image = new BitmapImage(new Uri(@"pack://application:,,,/Assets/wait.png", UriKind.Absolute));
        }
    
        return image;
    }
    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        SelectedNoManager.Instance.SelectedNo = 2;
    
     //   RaisePropertyChanged(this, "SampleRecords"); // 更新されない
    
        foreach (var item in _SampleRecords)
        {
            item.RaisePropertyChanged(item, "No");
        }
    }
     <Image Height="15" Source="{Binding No,Converter={StaticResource NoToImage}}" />
    #SampleRecordクラスにINotifyPropertyChangedを実装したコードは略


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年3月19日 6:19
    モデレータ

  • 返信ありがとうございます。

    遅くなってしまいましたが、今日返信があったことに気づきました。すいません。
    ご指摘のとおり、完全に個人の独学になっているため、バインディングの理解不足など様々な知識が凸凹になっていてまだまだ整理できていません。

    >ObservableCollectionは元々、INotifyPropertyChangedを実装しており
    WPFのデータグリッドに与えるコレクションはObservableCollectionである、のような曖昧な理解をしていました。
    きちんとリファレンスを読んでおけばよかったです。

    >時間がある時にでも、SampleRecordクラスにINotifyPropertyChangedを実装して試してみて下さい。このパターンはObservableCollectionを使う際の王道ですので、覚えておいて下さい。
    >開発を急がれていれば仕方ありませんが、自分の思うように動かなかったところを理解して先に進むことは、とても大切なことです。動かないコードをそのまま葬るのではなく、そういうコードから学ぶことはたくさんあるはずです。

    上記の部分については、ためしに今日休憩時間にささっとやってみました。
    http://1drv.ms/1I9u2sF

    うまくIValueConverterでも変化させることができました。
    特にObservableCollectionの基礎的な理解と、Binding通知の部分については少しずつでも理解を深めようと思います。

    技術的なフォローが頂けるとは思わなかったので、とてもうれしかったです。
    trapemiyaさん、ありがとうございました。
    2015年4月3日 6:00