【问题标题】:WPF: Accessing databound data in code behind each time the binding source is updated?WPF:每次更新绑定源时都在代码中访问数据绑定数据?
【发布时间】:2011-05-12 18:22:36
【问题描述】:

有没有办法在每次更新数据绑定数据时获取访问数据绑定数据的代码?我已经看到可以挂接到 DependencyProperty 的静态 ValidateValueCallback 委托,但这是静态的,实际上,它的目的只是为了验证。

在很多情况下,当数据绑定源更新时,我需要更新其他对象。一个例子是一个动画列表框,我需要为添加到框中的第一个新项目添加动画。因此,我需要访问新更新的绑定源项,以便确定哪些项是新的以及哪些项应该在列表框之外进行动画处理。

明确地说,我有一个带有 DependencyProperty 的 UserControl,数据源绑定到外部,而 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 是在 AnimatableObservableCollection 对象类型的 UserControl 中声明的 DependencyProperty。 AnimatableObservableCollection 扩展了 ObservableCollection。

【问题讨论】:

    标签: wpf listbox data-binding


    【解决方案1】:

    要清楚,我有一个 UserControl 一个数据的 DependencyProperty 源绑定到外部和一个 ListBox.ItemSource 绑定到 内部。

    这是否意味着用户控件的DataContext 是数据源,而用户控件 的ListBox 然后绑定到数据源?因为那是有道理的。否则,我不确定您的意思 - 您会编辑问题并分享您的一些代码/XAML,以便更清楚您正在使用的内容吗?

    假设我概述的场景,现在,ObservableCollection 听起来像是要走的路,而且我实际上已经能够仅在 XAML 中对 ItemsControl 的添加(以及可能从中删除)进行动画处理,而无需编写事件处理程序.

    假设您将 ListBox 的 ItemsSource 绑定到 ObservableCollection&lt;YourListItemDataObjects&gt;。您可以像下面这样创建一个DataTemplate 并将其分配给ListBox 的ItemTemplate 属性:

    <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 呈现为绑定到ObservableCollection 中对象的Name 属性的文本框(显然,将该属性更改为适合您的情况)。

    复杂的一点是动画。注意EventTrigger,其RoutedEvent 属性是"TextBlock.Loaded"。每当将项目添加到绑定到 ListBox 的 ObservableCollection 时,就会触发该事件,因为这会导致创建一个新的 ListBoxItem - 从而创建一个新的 TextBlock,其 Loaded 事件将触发。

    同样,您可以在移除项目时触发 Unloaded 事件。

    还请注意,ColorAnimationStoryboard.TargetName 属性指的是我们为构成 TextBlock 的背景的第二个 GradientStop 指定的名称。这告诉动画要更改 TextBlock 的视觉树中的哪个元素 - 典型的 WPF 动画总是应用于视觉元素的依赖属性。

    在 EventTrigger 上应用 animation 允许您在绑定数据源时将效果(在本例中为渐变颜色,但您也可以使用 Opacity 来进行淡入和淡出)到控件被改变了。

    示例中的第二个 EventTrigger 在用户单击此 TextBlock 时将发生的 MouseUp 事件上激活,它会删除我们在加载 TextBlock 时应用的动画(注意 AutoReverse="False" 设置在第一个动画上,这会导致它保持其结束状态,直到我们明确删除它)。

    我们现在有一个 ListBox,它的项目在添加时会“发光”几秒钟,并且在我们单击它们之前保持突出显示的颜色。

    显然,这只是一个起点 - 数据模板和动画都是您可能想要进一步研究的深层主题。但我希望您发现这是 WPF 强大绑定功能的一个有用示例,以及它们让您仅通过 XAML 定义 UI 的潜力。

    【讨论】:

    • 首先,感谢您非常详细的回复。但是,我的代码安排与您描述的相同,而且效果很好。好吧,我发现如果我有一个由 ListBoxItem.Unloaded 事件触发的动画,我将永远看不到它,因为该项目一卸载就会消失。
    • 因此,我必须在它在后面的代码中卸载之前添加一个动画。同样在代码中,我需要将新项目与旧项目进行比较,并对旧集合中的项目执行“退出幻灯片”动画,而不是对新项目执行新项目和“进入幻灯片”动画。这就是为什么我需要在更新项目时访问后面的代码。
    • 另一件事 - 当我将 UserControl 的 DataContext 属性设置为“this”时,项目根本不会出现。所以我可以让它工作的唯一方法是在每个绑定上使用 ElementName="UserControlName"。
    • @Sheridan 很好的阐述 - 您可能希望将其重新合并到原始问题中。我仍然希望有一种方法可以在不通过代码攻击视觉树的情况下制作这些动画(尽管如果其他人想提出这样的解决方案,请成为我的客人)。为此,this SO questionthis demo code 可能有用...
    • 再次感谢。我刚刚快速浏览了您的链接,并且将动画放在列表框内的自定义面板中的想法听起来很有趣。我现在没时间深入,但有机会我一定会好好看看的。
    【解决方案2】:

    如果您的绑定是两种方式,那么您的底层数据源对象应该与前端同步更改。您可以通过在其上实现 INotifyPropertyChanged 并将处理程序附加到事件来捕获对此对象的任何更改。
    http://msdn.microsoft.com/en-us/library/ms743695.aspx

    【讨论】:

    • 如果是单向源,这种技术仍然适用于从底层数据源更新 UI。
    • 我的数据类型确实实现了 INotifyPropertyChanged,但问题不是“UI 未更新”问题,而是“无法对来自代码后面的绑定数据源中的更改做出反应”的问题。
    【解决方案3】:

    也许可以尝试一种不同的方法来识别新项目何时添加到 ListBox。与其尝试对告诉您数据已更改的 UI 做出反应,不如倾听数据本身的更改。

    例如,如果您的 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....
            }
        }
    

    【讨论】:

    • 我已经尝试过了,但是程序执行从来没有进入事件处理方法......我不知道为什么不。
    • 正是……除了我有一个从 ObservableCollection 扩展而来的 AnimatedObservableCollection,但我不明白为什么会有任何不同。
    【解决方案4】:

    我发现如果我在 DependencyProperty 声明中使用 UIPropertyMetadata 对象而不是 FrameworkPropertyMetadata 对象,那么我可以附加一个 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)转换为控件/类的类型,然后您可以调用类中的任何非静态成员。如果您的回调处理程序代码只是一个操作(例如,方法调用或赋值等),那么您可以完全不使用处理程序,而是使用 lambda 表达式。如下所示:

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

    最后要注意的是,如果您想提供默认值,则 UIPropertyMetadata 构造函数的重载允许您提供默认值,然后是 PropertyChangedCallback 处理程序。我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多