与 CollectionViewSource 绑定

发布于 2024-10-20 08:29:36 字数 1181 浏览 4 评论 0原文

我正在尝试使用 CollectionViewSource 实现一些组合框排序。该组合框实际上是数据模板的一部分,并在列表视图中重复出现。我的第一种方法似乎有效(使用 CollectionViewSource),但我的所有组合框共享相同的数据上下文。这使得每当其他盒子之一发生变化时,所有其他盒子都会发生变化以反映 - 这不是期望的副作用。

我决定回过头来尝试使用内联 xaml 来指定 CollectionViewSource(而不是将 cvs 创建为静态资源)来实现基本组合框(不在数据模板内)。我无法成功显示我的数据。我的想法可能完全错误,因为我对 WPF 还很陌生。

下面是我的组合框的 xaml:

<ComboBox>
    <ComboBox.ItemsSource>
        <Binding>
            <Binding.Source>
                <CollectionViewSource Source="{Binding Path=Configurations}">
                    <CollectionViewSource.SortDescriptions>
                        <scm:SortDescription PropertyName="AgencyName" />
                    </CollectionViewSource.SortDescriptions>
                </CollectionViewSource>
            </Binding.Source>
        </Binding>
    </ComboBox.ItemsSource>
</ComboBox>

此组合框所在的用户控件的 DataContext 绑定到一个对象,该对象具有名为 Configurations 的 ObservableCollection,并且每个配置都有一个名为 AgencyName 的属性。我已经验证,使用没有 cvs 的标准绑定可以正常工作,所以我知道一切都很好。

任何帮助将不胜感激,因为我已经没有借口给我的老板了:)。我也不想进入代码并在后面的代码中进行排序(当我构建 ObservableCollection 时我可以这样做,但恕我直言,这违反了 DRY 原则)。

I am trying to implement some combo box sorting using CollectionViewSource. This combo box is actually part of a data template and is repeated in a list view. My first approach seemed to work (using CollectionViewSource) but all my combo boxes shared the same data context. This made it so whenever one of the other boxes was changed all the others changed to reflect - not a desired side effect.

I decided to just pull back and try to implement a basic combo box (not inside a data template) using inline xaml for specifying the CollectionViewSource (as opposed to creating the cvs as a static resource). I have not been able to successfully get my data to display. I'm probably going about this entirely wrong as I'm still new to WPF.

Here is the xaml for my combo box:

<ComboBox>
    <ComboBox.ItemsSource>
        <Binding>
            <Binding.Source>
                <CollectionViewSource Source="{Binding Path=Configurations}">
                    <CollectionViewSource.SortDescriptions>
                        <scm:SortDescription PropertyName="AgencyName" />
                    </CollectionViewSource.SortDescriptions>
                </CollectionViewSource>
            </Binding.Source>
        </Binding>
    </ComboBox.ItemsSource>
</ComboBox>

The DataContext of the user control where this combo box lives is bound to an object which has an ObservableCollection called Configurations and each configuration has a property called AgencyName. I've verified that this works fine using standard binding without the cvs so I know everything is fine in that accord.

Any help would be greatly appreciated as I have ran out of excuses to my boss :). I also don't want to have to drop down into code and do the sorting in the code behind (which I could when I build the ObservableCollection but IMHO that violates the DRY principle).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

瑶笙 2024-10-27 08:29:36

“每当其他盒子之一发生变化时,所有其他盒子都会发生变化以反映”到底是什么意思?您是在谈论 SelectedItem 吗?
如果是这样,那么在 ComboBox 中设置 IsSynchronizedWithCurrentItem = false 可能会有所帮助。

除此之外:我认为只要您在后面的代码中创建并排序 ICollectionView 一次,就不会违反 DRY 原则,因为您在 XAML 中不再需要做更多的事情。但我发现可能还有其他原因说排序等功能应该在视图中完成,从模型-视图-视图模型的角度来说。

What exactly do you mean by "whenever one of the other boxes was changed all the others changed to reflect"? Are you talking about the SelectedItem?
If so, then it may help setting IsSynchronizedWithCurrentItem = false in your ComboBox.

Beside that: I think as long as you create and sort your ICollectionView in the code behind only once, there is no violation of the DRY principle, because what you do more there is not needed in the XAML anymore. But I see there may be other reasons to say that a feature like sorting should be done in the View, speaking in terms of Model-View-ViewModel.

长安忆 2024-10-27 08:29:36

没有阅读您的整篇文章,但问题是默认情况下资源是共享的。因此,每个组合框都引用相同的集合视图。集合视图包括跟踪选择,因此更改一个组合框中的选择会影响其他组合框中的选择。

您可以阻止 CVS 被共享,而不是将 CVS 移至本地资源:

<CollectionViewSource x:Key="whatever" x:Shared="False" .../>

Didn't read your entire post, but the problem is that resources are shared by default. Each combo box was therefore referencing the same collection view. A collection view includes tracking selection, so changing the selection in one combo box would affect the others.

Rather than move the CVS to a local resource, you could just prevent it from being shared:

<CollectionViewSource x:Key="whatever" x:Shared="False" .../>
无畏 2024-10-27 08:29:36

虽然可能为时已晚,但我将这个答案留给可能遇到此问题的其他人。
您对 CollectionViewSource.Source 的绑定不起作用,因为 CollectionViewSource 不属于视觉/逻辑树,并且它既不继承数据上下文,也不能够引用 ComboBox 作为绑定源。我能够使用以下类以一种丑陋但简单的方式解决这个问题:

/// <summary>
/// Provides a way to set binding between a control
/// and an object which is not part of the visual tree.
/// </summary>
/// <remarks>
/// A bright example when you need this class is having an 
/// <see cref="ItemsControl"/> bound to a <see cref="CollectionViewSource"/>.
/// The tricky thing arises when you want the <see cref="CollectionViewSource.Source"/>
/// to be bound to some property of the <see cref="ItemsControl"/> 
/// (e.g. to its data context, and to the view model). Since 
/// <see cref="CollectionViewSource"/> doesn't belong to the visual/logical tree,
/// its not able to reference the <see cref="ItemsControl"/>. To stay in markup,
/// you do the following:
/// 1) Add an instance of the <see cref="BindingBridge"/> to the resources 
/// of some parent element;
/// 2) On the <see cref="ItemsControl"/> set the <see cref="BindingBridge.BridgeInstance"/> attached property to the
/// instance created on step 1) using <see cref="StaticResourceExtension"/>;
/// 3) Set the <see cref="CollectionViewSource.Source"/> to a binding which has 
/// source set (via <see cref="StaticResourceExtension"/>) to <see cref="BindingBridge"/>  
/// and path set to the <see cref="BindingBridge.SourceElement"/> (which will be the control 
/// on which you set the attached property on step 2) plus the property of interest
/// (e.g. <see cref="FrameworkElement.DataContext"/>):
/// <code>
///  <CollectionViewSource
///     Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}}"/>
/// </code>.
/// 
/// So the result is that when assigning the attached property on a control, the assigned 
/// <see cref="BindingBridge"/> stores the reference to the control. And that reference can be 
/// retrieved from the <see cref="BindingBridge.SourceElement"/>.
/// </remarks>
public sealed class BindingBridge : DependencyObject
{
    #region BridgeInstance property

    public static BindingBridge GetBridgeInstance(DependencyObject obj)
    {
        Contract.Requires(obj != null);
        return (BindingBridge)obj.GetValue(BridgeInstanceProperty);
    }

    public static void SetBridgeInstance(DependencyObject obj, BindingBridge value)
    {
        Contract.Requires(obj != null);
        obj.SetValue(BridgeInstanceProperty, value);
    }

    // Using a DependencyProperty as the backing store for BridgeInstance.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BridgeInstanceProperty =
        DependencyProperty.RegisterAttached("BridgeInstance", typeof(BindingBridge), typeof(BindingBridge),
        new PropertyMetadata(OnBridgeInstancePropertyChanged));

    #endregion BridgeInstance property

    #region SourceElement property

    public FrameworkElement SourceElement
    {
        get { return (FrameworkElement)GetValue(SourceElementProperty); }
        private set { SetValue(SourceElementPropertyKey, value); }
    }

    // Using a DependencyProperty as the backing store for SourceElement.  This enables animation, styling, binding, etc...
    private static readonly DependencyPropertyKey SourceElementPropertyKey =
        DependencyProperty.RegisterReadOnly("SourceElement", typeof(FrameworkElement), typeof(BindingBridge), new PropertyMetadata(null));

    public static readonly DependencyProperty SourceElementProperty;

    #endregion SourceElement property

    /// <summary>
    /// Initializes the <see cref="BindingBridge"/> class.
    /// </summary>
    static BindingBridge()
    {
        SourceElementProperty = SourceElementPropertyKey.DependencyProperty;
    }

    private static void OnBridgeInstancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var sourceElement = (FrameworkElement)d;
        var bridge = (BindingBridge)e.NewValue;
        bridge.SourceElement = sourceElement;
    }
}

这是一个用法示例(未显示资源字典):

 <ItemsControl
        infrastructure:BindingBridge.BridgeInstance="{StaticResource ImagesBindingBridge}">
        <ItemsControl.ItemsSource>
            <Binding>
                <Binding.Source>
                    <CollectionViewSource
                                Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}, Mode=OneWay}">
                        <CollectionViewSource.SortDescriptions>
                            <componentModel:SortDescription PropertyName="Timestamp" Direction="Descending"/>
                        </CollectionViewSource.SortDescriptions>
                    </CollectionViewSource>
                </Binding.Source>
            </Binding>
        </ItemsControl.ItemsSource>
    </ItemsControl>

While its probably too late, I'm leaving this answer for others who may encounter this problem.
Your binding for the CollectionViewSource.Source doesn't work because the CollectionViewSource doesn't belong to the visual/logical tree, and it neither inherits the Data Context nor its able to reference the ComboBox as a source of binding. I was able to solve this in an ugly, but simple way using the following class:

/// <summary>
/// Provides a way to set binding between a control
/// and an object which is not part of the visual tree.
/// </summary>
/// <remarks>
/// A bright example when you need this class is having an 
/// <see cref="ItemsControl"/> bound to a <see cref="CollectionViewSource"/>.
/// The tricky thing arises when you want the <see cref="CollectionViewSource.Source"/>
/// to be bound to some property of the <see cref="ItemsControl"/> 
/// (e.g. to its data context, and to the view model). Since 
/// <see cref="CollectionViewSource"/> doesn't belong to the visual/logical tree,
/// its not able to reference the <see cref="ItemsControl"/>. To stay in markup,
/// you do the following:
/// 1) Add an instance of the <see cref="BindingBridge"/> to the resources 
/// of some parent element;
/// 2) On the <see cref="ItemsControl"/> set the <see cref="BindingBridge.BridgeInstance"/> attached property to the
/// instance created on step 1) using <see cref="StaticResourceExtension"/>;
/// 3) Set the <see cref="CollectionViewSource.Source"/> to a binding which has 
/// source set (via <see cref="StaticResourceExtension"/>) to <see cref="BindingBridge"/>  
/// and path set to the <see cref="BindingBridge.SourceElement"/> (which will be the control 
/// on which you set the attached property on step 2) plus the property of interest
/// (e.g. <see cref="FrameworkElement.DataContext"/>):
/// <code>
///  <CollectionViewSource
///     Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}}"/>
/// </code>.
/// 
/// So the result is that when assigning the attached property on a control, the assigned 
/// <see cref="BindingBridge"/> stores the reference to the control. And that reference can be 
/// retrieved from the <see cref="BindingBridge.SourceElement"/>.
/// </remarks>
public sealed class BindingBridge : DependencyObject
{
    #region BridgeInstance property

    public static BindingBridge GetBridgeInstance(DependencyObject obj)
    {
        Contract.Requires(obj != null);
        return (BindingBridge)obj.GetValue(BridgeInstanceProperty);
    }

    public static void SetBridgeInstance(DependencyObject obj, BindingBridge value)
    {
        Contract.Requires(obj != null);
        obj.SetValue(BridgeInstanceProperty, value);
    }

    // Using a DependencyProperty as the backing store for BridgeInstance.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BridgeInstanceProperty =
        DependencyProperty.RegisterAttached("BridgeInstance", typeof(BindingBridge), typeof(BindingBridge),
        new PropertyMetadata(OnBridgeInstancePropertyChanged));

    #endregion BridgeInstance property

    #region SourceElement property

    public FrameworkElement SourceElement
    {
        get { return (FrameworkElement)GetValue(SourceElementProperty); }
        private set { SetValue(SourceElementPropertyKey, value); }
    }

    // Using a DependencyProperty as the backing store for SourceElement.  This enables animation, styling, binding, etc...
    private static readonly DependencyPropertyKey SourceElementPropertyKey =
        DependencyProperty.RegisterReadOnly("SourceElement", typeof(FrameworkElement), typeof(BindingBridge), new PropertyMetadata(null));

    public static readonly DependencyProperty SourceElementProperty;

    #endregion SourceElement property

    /// <summary>
    /// Initializes the <see cref="BindingBridge"/> class.
    /// </summary>
    static BindingBridge()
    {
        SourceElementProperty = SourceElementPropertyKey.DependencyProperty;
    }

    private static void OnBridgeInstancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var sourceElement = (FrameworkElement)d;
        var bridge = (BindingBridge)e.NewValue;
        bridge.SourceElement = sourceElement;
    }
}

Here is an example of usage (the resource dictionary isn't shown):

 <ItemsControl
        infrastructure:BindingBridge.BridgeInstance="{StaticResource ImagesBindingBridge}">
        <ItemsControl.ItemsSource>
            <Binding>
                <Binding.Source>
                    <CollectionViewSource
                                Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}, Mode=OneWay}">
                        <CollectionViewSource.SortDescriptions>
                            <componentModel:SortDescription PropertyName="Timestamp" Direction="Descending"/>
                        </CollectionViewSource.SortDescriptions>
                    </CollectionViewSource>
                </Binding.Source>
            </Binding>
        </ItemsControl.ItemsSource>
    </ItemsControl>
裂开嘴轻声笑有多痛 2024-10-27 08:29:36

绑定依赖于 VisualTree,而 cvs 不是视觉对象,因此绑定不起作用。

您可以使用 x:Reference 代替。

<Border x:Name="border" />
<ComboBox>
    <ComboBox.ItemsSource>
        <Binding>
            <Binding.Source>
                <CollectionViewSource Source="{Binding Path=DataContext.Configurations, Source={x:Reference border}}">
                    <CollectionViewSource.SortDescriptions>
                        <scm:SortDescription PropertyName="AgencyName" />
                    </CollectionViewSource.SortDescriptions>
                </CollectionViewSource>
            </Binding.Source>
        </Binding>
    </ComboBox.ItemsSource>
</ComboBox>

Binding depends on VisualTree which cvs is not a visual, so Binding doesn't work.

You can use x:Reference instead.

<Border x:Name="border" />
<ComboBox>
    <ComboBox.ItemsSource>
        <Binding>
            <Binding.Source>
                <CollectionViewSource Source="{Binding Path=DataContext.Configurations, Source={x:Reference border}}">
                    <CollectionViewSource.SortDescriptions>
                        <scm:SortDescription PropertyName="AgencyName" />
                    </CollectionViewSource.SortDescriptions>
                </CollectionViewSource>
            </Binding.Source>
        </Binding>
    </ComboBox.ItemsSource>
</ComboBox>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文