WPF 数据绑定:CollectionViewSource 和 ObjectDataProvider 问题

发布于 2024-10-04 23:57:54 字数 3332 浏览 3 评论 0原文

我有一个 MainWindow.xaml 文件:

<Window.Resources>

  <CollectionViewSource x:Key="cvs" 
    Source="{Binding Source={StaticResource ResourceKey=DetailsCollection}}" />

  <CollectionViewSource x:Key="DetailScopes">
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

  <DataTemplate x:Key="AccountDetail"
    DataType="{x:Type entities:AccountDetail}">
    <DockPanel>
      <ComboBox 
        DockPanel.Dock="Left" 
        ItemsSource="{Binding Source={StaticResource ResourceKey=DetailScopes}}" 
        SelectedItem="{Binding Path=Scope}">
        <ComboBox.ItemTemplate>
          <DataTemplate>
            <TextBlock 
              Text="{Binding Converter={StaticResource DetailScopeConverter}}" />
          </DataTemplate>
        </ComboBox.ItemTemplate>
      </ComboBox>
      <TextBox Text="{Binding Path=Value}" />
    </DockPanel>
  </DataTemplate>

</Window.Resources>

...

<ListBox 
  ItemTemplate="{StaticResource ResourceKey=AccountDetail}" 
  ItemsSource="{Binding Source={StaticResource ResourceKey=cvs}}" />

及其代码隐藏类,我在其中定义了详细范围的过滤器:

public class MainWindow
{
    public MainWindow()
    {
        CollectionViewSource detailScopes;

        InitializeComponent();

        // Attach filter to the collection view source
        detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
        detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);

        private void DetailScopesFilter(object sender, FilterEventArgs e)
        {
            DetailScope scope;

            scope = (DetailScope)e.Item;
            if (scope == DetailScope.Private ||
                scope == DetailScope.Business)
            {
                e.Accepted = true;
            }
            else
            {
                e.Accepted = false;
            }
        }
    }
}

接下来,有 AccountDetail 类:

public class AccountDetail
{
  public string Value
  {
    get { return this.value; }
    set { this.value = value; }
  }
  public DetailScope Scope
  {
    get { return scope; }
    set { scope = value; }
  }

  private string value;
  private DetailScope scope;
}

最后,一个枚举:

public enum DetailScope
{
  Private, 
  Business, 
  Other
}

当我运行代码时,我获取一个填充了一堆帐户详细信息的列表框,每个详细信息都有自己的组合框(包含选定的范围)和一个包含适当值的文本框。问题是组合框中的所有选定值都与最后输入的详细信息设置的范围相匹配,并且更改任何组合框值都会更新所有这些值,就好像它们都绑定到相同的帐户详细信息一样。

当我从 CollectionViewSource DetailScopes 中取出 ObjectDataProvider 并将其直接绑定到 DataTemplate 中组合框的 ItemsSource 时AccountDetail,问题就解决了。但是,我确实需要在 CollectionViewSource 中使用它,因为我正在对其应用一些过滤,并且无法对 ObjectDataProvider 应用过滤。

有人可以解释为什么会发生这种情况以及我实际上应该如何连接 CollectionViewSourceObjectDataProvider 吗?谢谢。

I have a MainWindow.xaml file:

<Window.Resources>

  <CollectionViewSource x:Key="cvs" 
    Source="{Binding Source={StaticResource ResourceKey=DetailsCollection}}" />

  <CollectionViewSource x:Key="DetailScopes">
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

  <DataTemplate x:Key="AccountDetail"
    DataType="{x:Type entities:AccountDetail}">
    <DockPanel>
      <ComboBox 
        DockPanel.Dock="Left" 
        ItemsSource="{Binding Source={StaticResource ResourceKey=DetailScopes}}" 
        SelectedItem="{Binding Path=Scope}">
        <ComboBox.ItemTemplate>
          <DataTemplate>
            <TextBlock 
              Text="{Binding Converter={StaticResource DetailScopeConverter}}" />
          </DataTemplate>
        </ComboBox.ItemTemplate>
      </ComboBox>
      <TextBox Text="{Binding Path=Value}" />
    </DockPanel>
  </DataTemplate>

</Window.Resources>

...

<ListBox 
  ItemTemplate="{StaticResource ResourceKey=AccountDetail}" 
  ItemsSource="{Binding Source={StaticResource ResourceKey=cvs}}" />

and its code-behind class, where I defined the filter for detail scopes:

public class MainWindow
{
    public MainWindow()
    {
        CollectionViewSource detailScopes;

        InitializeComponent();

        // Attach filter to the collection view source
        detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
        detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);

        private void DetailScopesFilter(object sender, FilterEventArgs e)
        {
            DetailScope scope;

            scope = (DetailScope)e.Item;
            if (scope == DetailScope.Private ||
                scope == DetailScope.Business)
            {
                e.Accepted = true;
            }
            else
            {
                e.Accepted = false;
            }
        }
    }
}

Next, there's the AccountDetail class:

public class AccountDetail
{
  public string Value
  {
    get { return this.value; }
    set { this.value = value; }
  }
  public DetailScope Scope
  {
    get { return scope; }
    set { scope = value; }
  }

  private string value;
  private DetailScope scope;
}

Finally, an enum:

public enum DetailScope
{
  Private, 
  Business, 
  Other
}

When I run my code, I get a list box populated with a bunch of account details, each having its own combo box with a selected scope and a text box with the appropriate value. The problem is that all the selected values in the combo boxes match the scope set for the last entered detail and changing any of the combo box values updates all of them, as if they are all bound to the same account detail.

When I take out the ObjectDataProvider from the CollectionViewSource DetailScopes and bind it directly to the combo box's ItemsSource in the DataTemplate AccountDetail, the problem is gone. However, I do need it inside the CollectionViewSource because I am applying some filtering to it and I cannot apply filtering to ObjectDataProvider.

Could someone please explain why is this happening and how am I actually supposed to connect CollectionViewSource and ObjectDataProvider? Thank you.

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

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

发布评论

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

评论(1

纵山崖 2024-10-11 23:57:54

您的代码的问题是每个 ComboBox 都使用相同的 CollectionViewSource 实例;这意味着,带有“DetailScopes”键的资源由所有 ComboBox 共享,因此每当您从特定 ComboBox 中选择一个值时,它会自动在所有 ComboBox 中选择相同的值。 这是因为共享的底层集合会跟踪所选项目,并且由于它在从一个 ComboBox 中进行选择时发生变化,因此 CollectionViewSource 会将更改通知给所有 ComboBox

所以解决办法很简单。使 DetailScopes 资源不可共享所需的一切。

这是修复方法:

<!-- Please note this x:Shared="False" just after x:Key="DetailsScopes" --->

<CollectionViewSource x:Key="DetailScopes"  x:Shared="False"> 
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

希望它能解决您的问题!

然而,这个解决方案会带来另一个问题。让我引用一下 MSDN 中的一些内容,以便您了解 x:Shared 的作用。

x:共享属性

设置为 false 时,修改 WPF 资源检索
行为,以便请求
属性资源创建一个新的
每个请求的实例
而不是
为所有人共享同一个实例
请求。

由于 x:Shared 会在您尝试访问资源时创建资源的新实例(新副本),这意味着 Filter 处理程序方法仅附加到您在代码隐藏中获得的实例,而不是所有实例。

因此,为了使您的处理程序正常工作,您需要从 XAML 本身附加处理程序,如下所示:

<!-- Now please note Filter="DetailsScopesFilter" --->

<CollectionViewSource x:Key="DetailScopes"  x:Shared="False"  Filter="DetailScopesFilter"> 
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

希望它能解决您的所有问题。如果您仍然面临任何问题,请告诉我。:-)

哦,顺便说一句,不再需要以下代码隐藏。所以请删除它。

    // Attach filter to the collection view source
    detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
    detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);

.

The problem with your code is that every ComboBox is using the same instance of CollectionViewSource; that means, the resource with key "DetailScopes" is shared by all ComboBox, so whenever you select one value from a particular ComboBox, it automatically selects the same value in all ComboBox. It's because the underlying collection which is shared, keeps track of the selected item, and since it changes on selecting from one ComboBox, the CollectionViewSource notifies the change to ALL ComboBox.

So the solution is very simple. All you need to make DetailScopes resource unsharable.

Here is the fix:

<!-- Please note this x:Shared="False" just after x:Key="DetailsScopes" --->

<CollectionViewSource x:Key="DetailScopes"  x:Shared="False"> 
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

Hope it solves your problem!

However, this solution will cause another problem. Let me quote something from MSDN, so that you'll understand what x:Shared does.

x:Shared Attribute

When set to false, modifies WPF resource-retrieval
behavior so that requests for the
attributed resource create a new
instance for each request
instead of
sharing the same instance for all
requests.

Since x:Shared causes to create a new instance (a new copy) of the resource whenever you attempt to access it, that means, the Filter handler method is attached only to the instance which you get in the code-behind, not all the instances.

So in order to work your handler properly, you need to attach the Handler from XAML itself, like this:

<!-- Now please note Filter="DetailsScopesFilter" --->

<CollectionViewSource x:Key="DetailScopes"  x:Shared="False"  Filter="DetailScopesFilter"> 
    <CollectionViewSource.Source>
      <ObjectDataProvider 
        MethodName="GetValues" 
        ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="entities:DetailScope" />
        </ObjectDataProvider.MethodParameters>
      </ObjectDataProvider>
    </CollectionViewSource.Source>
  </CollectionViewSource>

Hope it solves all your problems. Let me know if you still face any.:-)

Oh by the way, the following code-behind is not needed anymore. So please remove it.

    // Attach filter to the collection view source
    detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
    detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);

.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文