WPF: Доступ к данным с привязкой к данным в коде после каждого обновления источника привязки? - PullRequest
2 голосов
/ 19 ноября 2010

Есть ли способ получить код за доступом к данным с привязкой к данным при каждом обновлении? Я видел статический делегат ValidateValueCallback, который можно подключить к DependencyProperty, но это статично и действительно, его цель только для проверки.

У меня есть несколько ситуаций, когда мне нужно обновлять другие объекты при обновлении источника данных. Одним из примеров является анимированный ListBox, где мне нужно добавить анимацию только к первому новому элементу, добавленному в поле. Поэтому мне нужен доступ к недавно обновленным исходным элементам привязки, чтобы я мог определить, какие элементы являются новыми, а какие элементы следует анимировать из списка.

Чтобы было ясно, у меня есть UserControl с DependencyProperty, к которому источник данных привязан извне, а ListBox.ItemSource связан с внутренне.

Я думаю, что должен быть простой способ достичь этого, но после нескольких дней поиска я все еще не нашел никаких примеров.

В ответ на ответ DJacobson, вот пример кода:

Внутри UserControl у нас есть ListBox:

    <ListBox Name="TheAnimatedListBox" ItemsSource="{Binding QueueItems, ElementName=UserControlName}" 
ItemContainerStyle="{DynamicResource QueueItemStyle}" HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
MouseDown="QueueItemsListBox_MouseDown" MinHeight="300" MinWidth="300">

UserControl не имеет установленного DataContext и объявляется так:

<Controls:AnimatedQueue Grid.Column="0" Grid.Row="1" x:Name="FirstResponseQueue"
QueueItems="{Binding FirstResponseItems}" />

QueueItems - это DependencyProperty, объявленный в UserControl объекта типа AnimatableObservableCollection. AnimatableObservableCollection расширяет ObservableCollection.

Ответы [ 4 ]

2 голосов
/ 19 ноября 2010

Чтобы было ясно, у меня есть UserControl с DependencyProperty, к которому источник данных привязан извне, а ListBox.ItemSource связан с внутренне.

Означает ли это, что UserControl's DataContext является источником данных, а ListBox внутри UserControl затем привязывается к источнику данных?Потому что это имело бы смысл.В противном случае, я не уверен, что вы имеете в виду - вы бы отредактировали вопрос и поделились частью своего кода / XAML, чтобы было немного более очевидно, с чем вы работаете?

Предполагая сценарий, который я обрисовал, длятеперь ObservableCollection звучит как путь, и я действительно смог анимировать дополнения (и, вероятно, удаления) из ItemsControl только в XAML, без написания обработчиков событий.

Допустим, вы связываете ItemsSource вашего ListBox с ObservableCollection<YourListItemDataObjects>.Вы можете создать DataTemplate, как показано ниже, и назначить его для свойства ItemTemplate ListBox:

<DataTemplate>
    <TextBlock Name="animatedTextBlock" Text="{Binding Name}">
        <TextBlock.Background>
            <LinearGradientBrush>
                <LinearGradientBrush.StartPoint>0.5,0.0</LinearGradientBrush.StartPoint>
                <LinearGradientBrush.EndPoint>0.5,1.0</LinearGradientBrush.EndPoint>
                <GradientStop Color="White" Offset="0.3"/>
                <GradientStop x:Name="cellBackgroundBottomStopColor" 
                              Color="Orange" Offset="0.9"/>
            </LinearGradientBrush>
        </TextBlock.Background>
        <TextBlock.Triggers>
            <EventTrigger SourceName="animatedTextBlock" 
                          RoutedEvent="TextBlock.Loaded">
                <BeginStoryboard Name="flashNewCell">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="cellBackgroundBottomStopColor" 
                                        Storyboard.TargetProperty="Color" 
                                        From="White" To="Orange" 
                                        Duration="0:0:1" AutoReverse="False"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger SourceName="animatedTextBlock" 
                          RoutedEvent="TextBlock.MouseUp">
                <RemoveStoryboard BeginStoryboardName="flashNewCell" />
            </EventTrigger>
        </TextBlock.Triggers>
    </TextBlock>
</DataTemplate>

Вы увидите, что DataTemplate вызовет визуализацию ListItems как TextBoxes, привязанных кName свойство объектов в вашем ObservableCollection (очевидно, измените это свойство на то, что подходит в вашем случае).

Сложный бит - это анимация.Обратите внимание на EventTrigger, свойство RoutedEvent которого равно "TextBlock.Loaded" .Это событие будет срабатывать всякий раз, когда элемент добавляется в ObservableCollection, привязанный к ListBox, поскольку это вызывает создание нового ListBoxItem - и, таким образом, нового TextBlock, чье событие Loaded сработает.

Аналогично, есть событие Unloaded, которое вы можете вызвать при удалении элемента.

Также обратите внимание, что свойство Storyboard.TargetName ColorAnimation относится к имени, которое мы дали второму GradientStop составляет фон TextBlock.Это сообщает анимации, какой элемент в визуальном дереве TextBlock нужно изменить - типичные анимации WPF всегда применяются к свойствам зависимостей визуальных элементов.

Применение animation к EventTrigger позволяет применятьэффекты (цвета градиента, в данном случае, но вы можете поиграть с Opacity, чтобы также делать постепенное исчезновение и исчезновение) для элемента управления, когда его связанный источник данных изменяется.

Второй EventTrigger впример активируется для события MouseUp, которое происходит, когда пользователь щелкает этот TextBlock, и удаляет анимацию, которую мы применяли, когда TextBlock был загружен (обратите внимание на настройку AutoReverse="False" для этой первой анимации, которая вызывает егосохранять конечное состояние до тех пор, пока мы не удалим его явно).

Теперь у нас есть ListBox, элементы которого «светятся» в течение нескольких секунд при добавлении, и который поддерживает выделенный цвет, пока мы не нажмем на них.

Очевидно, что это только отправная точка - шаблоны данных и анимацияОбе глубокие темы вы можете исследовать дальше.Но я надеюсь, что вы найдете это полезным примером мощных возможностей связывания WPF и их потенциала, позволяющего вам определять свой пользовательский интерфейс только с помощью XAML.

0 голосов
/ 08 февраля 2011

Я обнаружил, что если я использую объект UIPropertyMetadata вместо объекта FrameworkPropertyMetadata в объявлении DependencyProperty, то я могу присоединить обработчик PropertyChangedCallback, который вызывается при каждом обновлении DependencyProperty.Он используется следующим образом:

public readonly static DependencyProperty AnimatableItemsProperty = 
DependencyProperty.Register("AnimatableItems", typeof(ItemCollection), typeof
(YourClassNameHere), new UIPropertyMetadata(OnAnimatableItemsChanged));

private static void OnAnimatableItemsChanged(DependencyObject dependencyObject, 
DependencyPropertyChangedEventArgs e)
{
    ((YourClassNameHere)dependencyObject).CallAMethodOrPropertyHere(e);
}

public ItemCollection AnimatableItems
{
    get { return (ItemCollection)GetValue(AnimatableItemsProperty); }
    set { SetValue(AnimatableItemsProperty, value); }
}

Поскольку обработчик обратного вызова является статическим, вы должны привести ваш элемент управления (dependencyObject) к типу вашего элемента управления / класса, а затем вы можете вызывать любые нестатические члены втвой класс.Если ваш код обработчика обратного вызова - это всего лишь одна операция (например, вызов метода или присвоение и т. Д.), То вы можете вообще обойтись без обработчика и использовать вместо этого лямбда-выражение.Это будет выглядеть следующим образом:

public readonly static DependencyProperty AnimatableItemsProperty = 
DependencyProperty.Register("AnimatableItems", typeof(ItemCollection), typeof
(YourClassNameHere), new UIPropertyMetadata(
(d,e) => ((YourClassNameHere)d).CallAMethodOrPropertyHere(e)));

Последнее, что следует отметить, это то, что если вы хотите предоставить значение по умолчанию, существуют перегрузки конструктора UIPropertyMetadata, а один позволяет вам указать значение по умолчанию, после чего следуетобработчик PropertyChangedCallback.Надеюсь, это поможет.

0 голосов
/ 19 ноября 2010

Возможно, попробуйте другой подход к распознаванию, когда новый элемент добавлен в ListBox.Вместо того, чтобы пытаться реагировать на пользовательский интерфейс, сообщающий вам, что данные изменились, прислушайтесь к изменениям в самих данных.

Например, если ваш ListBox связан с ObservableCollection, то прослушайте событие CollectionChanged коллекции, чтобы уведомить васчто предмет был добавлен.

    private void TestObservableCollection()
    {
        // Create you Collection and handle the CollectionChanged event so that 
        // you know when items are being added or removed from the collection.
        ObservableCollection<Person> people = new ObservableCollection<Person>();
        people.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(people_CollectionChanged);
    }

    void people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        // Check if a new item was added to the ObservableCollection<Person>
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
        {
            // Do something in the UI here....
        }
    }
0 голосов
/ 19 ноября 2010

Если ваша привязка двусторонняя, то ваш базовый объект источника данных должен меняться синхронно с внешним интерфейсом. Вы можете зафиксировать любые изменения в этом объекте, внедрив в него INotifyPropertyChanged и прикрепив обработчики к событию.
http://msdn.microsoft.com/en-us/library/ms743695.aspx

...