c# - 数据绑定(bind)到不同 UserControl 中的 ObservableCollection - 如何保留当前选择?

标签 c# wpf data-binding mvvm observablecollection

问题范围于 2010-03-25 扩大

我最终弄清楚了我的问题,但是由于解决了原始问题,所以出现了一个新问题,因为我希望能够将赏金奖励给某人!!!

一旦我发现了我的问题,我很快就发现当 ObservableCollection 更新时,数据绑定(bind)的 ComboBox 重新填充了它的内容,但大多数选择都被清除了。

我假设在这种情况下,MVVM 会让我难以记住最后选择的项目。我有一个想法,但它似乎有点讨厌。我会将赏金奖励给为此提出一个很好的解决方案的人!

问题重写于 2010-03-24

我有两个用户控件,其中一个是具有 TabControl 的对话框,另一个是出现在所述 TabControl 中的对话框。我就叫他们糖果对话 CandyNameViewer 为了简单起见。还有一个名为 的数据管理类。追踪器 它管理信息存储,出于所有意图和目的,它只公开一个公共(public)属性,即 ObservableCollection。

我通过后面的代码在 CandyDialog 中显示 CandyNameViewer,如下所示:

private void CandyDialog_Loaded( object sender, RoutedEventArgs e)
{
  _candyviewer = new CandyViewer();
  _candyviewer.DataContext = _tracker;
  candy_tab.Content = _candyviewer;
}

CandyViewer 的 XAML 看起来像这样(为 kaxaml 编辑):
<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <DataTemplate x:Key="CandyItemTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"></ColumnDefinition>
                    <ColumnDefinition Width="150"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBox Grid.Column="0" Text="{Binding CandyName}" Margin="3"></TextBox>
                <!-- just binding to DataContext ends up using InventoryItem as parent, so we need to get to the UserControl -->
                <ComboBox Grid.Column="1" SelectedItem="{Binding SelectedCandy, Mode=TwoWay}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CandyNames}" Margin="3"></ComboBox>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <Grid>
        <ListBox DockPanel.Dock="Top" ItemsSource="{Binding CandyBoxContents, Mode=TwoWay}" ItemTemplate="{StaticResource CandyItemTemplate}" />
    </Grid>
</Page>

现在加载控件时一切正常。只要 CandyNames 被填充 第一个 ,然后显示消费者 UserControl,所有名称都在那里。我显然在输出窗口或类似的东西中没有任何错误。

我遇到的问题是,当从模型修改 ObservableCollection 时,这些更改是 不是 体现在消费者UserControl中!我以前从未遇到过这个问题;我以前对 ObservableCollection 的所有使用都更新得很好,尽管在那些情况下我没有跨程序集进行数据绑定(bind)。虽然我目前只在 ObservableCollection 中添加和删除糖果名称,但稍后我可能还会允许从模型端重命名。

是不是我做错了什么?有没有真正调试这个的好方法? Reed Copsey indicates here UserControl 间的数据绑定(bind)是可能的。不幸的是,我最喜欢的Bea Stollnitz article on WPF databinding debugging没有建议我可以用于这个特定问题的任何东西。

最佳答案

当 DataContext 更改时,ComboBoxes 出现空白时,我遇到了同样的问题。我有一个相对简单的解决方案,它使用我编写的名为“ComboBoxFixer”的类。一旦实现,您可以通过简单地替换这个来解决这个问题:

<ComboBox ItemsSource="..." SelectedItem="..." />

有了这个:
<ComboBox ItemsSource="..." my:ComboBoxFixer.SelectedItem="..." />

问题解释

您的 ComboBoxes 出现空的原因是未设置 ItemsSource 时正在评估 SelectedItems 绑定(bind)。这可以通过延迟与 SelectedItems 之间的数据传输直到所有其他数据绑定(bind)完成后才能解决。

如何实现 ComboBoxFixer

我的 ComboBoxFixer 类是使用我编写的通用依赖属性同步器类实现的。我的 DependencyPropertySynchronizer 类具有以下接口(interface):
public class DependencyPropertySynchronizer
{
  public DispatcherPriority Priority { get; set; }
  public DependencyProperty AutoSyncProperty { get; set; }

  public DependencyProperty Register(...
  public DependencyProperty RegisterAttached(...
  public DependencyPropertyKey RegisterReadOnly(...
  public DependencyPropertyKey RegisterAttachedReadOnly(...
}

并且通常这样使用:
public SomeClass : DependencyObject
{
  static DependencyPropertySynchronizer sync =
    new DependencyPropertySynchronizer
    {
      Priority = DispatcherPriority.ApplicationIdle
    };

  public static readonly DependencyProperty HappinessProperty =
   sync.RegisterAttached("Happiness", typeof(int), typeof(SomeClass));

  public static readonly DependencyProperty JoyProperty =
   sync.RegisterAttached("Joy", typeof(int), typeof(SomeClass));
}

上面的代码将导致任何给定对象的附加的幸福和快乐属性保持同步:无论何时设置幸福或快乐,另一个将在 DispatcherPriority.ApplicationIdle 中设置。 DependencyPropertySynchronizer 是使用隐藏的附加属性实现的,该属性存储在任一属性上设置的最后一个值并协调更新的调度。您还可以通过设置 AutoSyncProperty 与现有属性同步。

使用这个类,我的 ComboBoxFixer 类非常简单:
public class ComboBoxFixer : DependencyObject
{
  static DependencyPropertySynchronizer sync =
    new DependencyPropertySynchronizer
    {
      Priority = DispatcherPriority.ApplicationIdle,
      AutoSyncProperty = Selector.SelectedItemProperty,
    };

  public static readonly DependencyProperty SelectedItemProperty =
    sync.RegisterAttached("SelectedItem", typeof(object), typeof(ComboBoxFixer),
    new FrameworkPropertyMetadata
    {
      BindsTwoWayByDefault = true,
    });

  public static object GetSelectedItem(...  // normal attached property stuff
  public static void SetSelectedItem(...
}

它是如何工作的

每当 my:ComboBoxFixer.SelectedItem 更改时,同步器都会以 ApplicationIdle 优先级更新 Selector.SelectedItem,反之亦然。

数据流为:
ViewModel property
   <- bound from ->
      my:ComboBoxFixer.SelectedItem
         <- synced with ->
            ComboBox.SelectedItem

附加说明

在某些情况下,如果您实际上是在切换 ItemsSource,则 SelectedItem 可能比正确值更晚地设置为 null。这可以通过向 DependencyObjectSynchronizer 添加验证功能来解决,然后在同步期间使用它来忽略空值。

关于c# - 数据绑定(bind)到不同 UserControl 中的 ObservableCollection - 如何保留当前选择?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2480450/

相关文章:

c# - 使用 DataRow 的 DataBinding - 问题

c# - 如何防止应用程序在关闭窗口时关闭?

wpf - 如何跳过对禁用元素的验证?

c# - 如何关闭 Entity Framework 5 的复数表创建?

C# : catch all errors/exceptions of a mixed managed/unmanaged process

wpf - 我可以将数据绑定(bind)到 DataGridRow.DetailsVisibility 吗?

c# - 需要了解这个WPF程序中的事件触发。 .

asp.net - 如何从aspx页面调用代码隐藏方法?

c# - 当前类型 IUserStore<ApplicationUser> 和 DbConnection 分别是接口(interface)和抽象类,无法构造

c# - 为什么编译器对此代码的行为不同?