0

リストボックスとテキスト ボックスを含む基本的なコントロールがあり、リストボックスがオブジェクトのコレクションにバインドされ、基本的なデータ テンプレートがあるとします。

<DockPanel LastChildFill="True">
    <TextBlock DockPanel.Dock="Top">Book name</TextBlock>
    <TextBox x:Name="bookNameTextBox" DockPanel.Dock="Top" />
    <TextBlock DockPanel.Dock="Top">Authors</TextBlock>
    <ListBox ItemsSource="{Binding Authors}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</DockPanel>

public class Author : INotifyPropertyChanged
{
    public string Name { get; set; }
    public ObservableCollection<Book> Books { get; }
}

public class Book : INotifyPropertyChanged
{
    public string Name { get; }
}

私がやりたいことは、その著者が提供された名前に一致する本を持っているかどうかに応じて、リストボックス内のアイテムの色を変えることです。

Colour = author.Books.Any(b => b.Name.StartsWith(bookNameTextBox.Text)) ? Red : Black;

私は当初、MultiBinding とコンバーターを使用してこれを行うことができると考えていましたが、本のコレクションにアイテムが追加/削除されたとき、または本の名前が変更されたときにバインディングを更新する方法を理解できませんでした。

ロジックに影響を与える可能性のあるさまざまな変更のすべてに応じて、色が正しく更新されるようにするにはどうすればよいでしょうか? 例えば

  • 書籍名の変更
  • コレクションの追加と削除中の本
  • テキスト ボックスのテキストがbookNameTextBox変化する

私のMultiBindingはこのように見えました

<TextBlock.Style>
    <Style TargetType="TextBlock">
        <Style.Triggers>
            <DataTrigger Value="True">
                <DataTrigger.Binding>
                    <MultiBinding Converter="{StaticResource MyConverter}">
                        <Binding Path="Books" />
                        <Binding Path="Text" ElementName="bookNameTextBox" />
                    </MultiBinding>
                </DataTrigger.Binding>
                <Setter Property="Foreground" Value="Red" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</TextBlock.Style>

そして、私のコンバーター(実装IMultiValueConverter)は次のようになりました

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    var text = (string)values.First(v => v is string);
    var books = (IEnumerable<Book>)values.First(v => v is IEnumerable<Book>);
    return books.Any(b => b.Name.StartsWith(text));
}

これは機能しましたが、書籍を変更したり、書籍を追加したりすると、バインディングが何らかの形で更新されるまで、リスト項目のテキストの色が更新されませんでした。

4

3 に答える 3

0

私はあなたのコードを実行しようとしました。

bookNameTextbox.Textを変更すると、コンバーターが呼び出され、結果は正しくなります。

コードに欠けている部分があります。

PropertyChangedイベントを呼び出さないでください。ビューは変更に関する通知を受け取らないため、そうする必要があります。したがって、プロパティを設定した後、このイベントを呼び出します。

単純なバインディングを使用すれば十分です。つまりObservableCollection、ビュー内のアイテムが小道具の変更通知を正しく送信すると、ビューが更新され続けます。

この場合、MultiBindingまだそこに必要なものを使用している間、それはかなり奇妙だと思います。

このqaによると、バインディングが内部にあるMultiBinding場合ObservableCollection、この場合は、changedイベントを発生させる必要があります。

そのため、Booksコレクションを変更した後、BooksプロパティのPropertyChangedイベントを呼び出したところ、期待どおりの結果が得られました。

于 2012-09-13T14:34:37.403 に答える
0

あなたが探しているのはBinding Converterだと思います。これは、バインドされたすべてのアイテムに対して呼び出しを受け取り、決定ロジックを配置して適切な結果(あなたの場合は色)を返すことができます。

于 2012-09-13T12:05:29.997 に答える
0

このStackOverflowの質問とこのリンクされたコードサンプルに基づいて、大まかに満足できるソリューションを思いつきました。

AuthorInfoから継承する追加のクラスを作成し、FrameworkElementこのクラスのインスタンスを my の横にTextBlock配置しました。

<DataTemplate>
    <StackPanel>
        <Local:AuthorInfo Collection="{Binding Books}" Text="{Binding Text, ElementName=_bookNameTextBox}" x:Name="_authorInfo" />
        <TextBlock Text="{Binding Name}">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <DataTrigger Value="True" Binding="{Binding BookMatches, ElementName=_authorInfo}">
                            <Setter Property="Foreground" Value="Red" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </StackPanel>
</DataTemplate>

このクラスには、監視するコレクションと検索するテキスト値の両方の依存関係プロパティがありBookMatches、書籍が指定された文字列と一致するかどうかを示すプロパティを公開します。これは、トリガーがバインドされるプロパティです。

リストまたはリスト内の項目が変更されたときにプロパティ値が確実に更新されるようにするために、このクラスは、さまざまなプロパティ変更イベントの購読および購読解除を追跡します。

public class AuthorInfo : FrameworkElement
{
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(AuthorInfo), new PropertyMetadata(default(string), PropertyChangedCallback));

    public static readonly DependencyProperty CollectionProperty =
        DependencyProperty.Register("Collection", typeof (IEnumerable), typeof (AuthorInfo), new PropertyMetadata(default(IEnumerable), PropertyChangedCallback));

    private static readonly DependencyPropertyKey ValuePropertyKey =
        DependencyProperty.RegisterReadOnly("Value", typeof (bool), typeof (AuthorInfo), new PropertyMetadata(default(bool)));

    public static readonly DependencyProperty ValueProperty = ValuePropertyKey.DependencyProperty;

    public bool BookMatches
    {
        get
        {
            return (bool) GetValue(ValueProperty);
        }
        set
        {
            SetValue(ValuePropertyKey, value);
        }
    }

    public IEnumerable Collection
    {
        get
        {
            return (IEnumerable)GetValue(CollectionProperty);
        }
        set
        {
            SetValue(CollectionProperty, value);
        }
    }

    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }

    protected void UpdateValue()
    {
        var books = Collection == null ? Enumerable.Empty<Book>() : Collection.Cast<Book>();
        BookMatches = !string.IsNullOrEmpty(Text) && books.Any(b => b.Name.StartsWith(Text));
    }

    private void CollectionSubscribe(INotifyCollectionChanged collection)
    {
        if (collection != null)
        {
            collection.CollectionChanged += CollectionOnCollectionChanged;
            foreach (var item in (IEnumerable)collection)
            {
                CollectionItemSubscribe(item as INotifyPropertyChanged);
            }
        }
    }

    private void CollectionUnsubscribe(INotifyCollectionChanged collection)
    {
        if (collection != null)
        {
            collection.CollectionChanged -= CollectionOnCollectionChanged;
            foreach (var item in (IEnumerable)collection)
            {
                CollectionItemUnsubscribe(item as INotifyPropertyChanged);
            }
        }
    }

    private void CollectionItemSubscribe(INotifyPropertyChanged item)
    {
        if (item != null)
        {
            item.PropertyChanged += ItemOnPropertyChanged;
        }
    }

    private void CollectionItemUnsubscribe(INotifyPropertyChanged item)
    {
        if (item != null)
        {
            item.PropertyChanged -= ItemOnPropertyChanged;
        }
    }

    private void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        if (args.OldItems != null)
        {
            foreach (var item in args.OldItems)
            {
                CollectionItemUnsubscribe(item as INotifyPropertyChanged);
            }
        }
        if (args.NewItems != null)
        {
            foreach (var item in args.NewItems)
            {
                CollectionItemSubscribe(item as INotifyPropertyChanged);
            }
        }
        UpdateValue();
    }

    private void ItemOnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        UpdateValue();
    }

    private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        var aggregator = (AuthorInfo)dependencyObject;
        if (args.Property == CollectionProperty)
        {
            aggregator.CollectionUnsubscribe(args.OldValue as INotifyCollectionChanged);
            aggregator.CollectionSubscribe(args.NewValue as INotifyCollectionChanged);
        }
        aggregator.UpdateValue();
    }
}

このサブスクリプション/サブスクリプション解除のビジネスが難しいということではなく、少し面倒なだけです。このようにして、面倒な変更通知がプレゼンテーション ロジックから分離されます。また、このロジックを他のタイプの集計に再利用できるように、すべての変更通知を基本クラスに含めるようにリファクタリングするのも簡単です。

于 2012-09-13T14:56:59.557 に答える