【问题标题】:WPF: Binding is lost when bindings are updatedWPF:更新绑定时绑定丢失
【发布时间】:2016-09-20 06:15:18
【问题描述】:

我有一个用于控制 WCF-RESTful 服务的 WPF 应用程序,即用于启动、初始化和停止它。因此,我有一个 MainWindow UI,其中包含一个 UserControl 来配置设置。当我初始化我的服务时,一些数据被加载到DependencyPropertiesObservableCollections 以在 GUI 中显示。这是我更新这些设置的方法的一部分:

public partial class MainWindow : Window {
    private void InitializeService (bool reInitialize = false) {
        var restService = (RestService)this.ServiceHost.SingletonInstance;
        var settings = restService.GetSettings();
        //UCSettings is the "x:name" of the embedded UserControl "UserControlSettings" in this window
        this.UCSettings.ExecutionTimes.Clear();
        settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
        this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");
    }
}


public partial class UserControlSettings : UserControl {
    public ObservableCollection<ExecutionTime> ExecutionTimes { get; set; }
    public static readonly DependencyProperty TableConfigurationLoaderProperty = DependencyProperty.Register("TableConfigurationLoader", typeof(Setting), typeof(UserControlSettings), new FrameworkPropertyMetadata(default(Setting)));
    public Setting TableConfigurationLoader {
        get { return (Setting)this.GetValue(TableConfigurationLoaderProperty); }
        set { this.SetValue(TableConfigurationLoaderProperty, value); }
    }
}

public class Setting {
    public string Name { get; set; }

    public bool IsEnabled { get; set; }

    public int ExecutionTimeId { get; set; }
}

public class ExecutionTime {
    public int Id { get; set; }

    public string Value { get; set; }
}

在代码设计器 (UserControlSettings.xaml.cs) 中,这些属性用于 ComboBox 的某些绑定中:

<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
    <ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>
</UserControl>

当我第一次使用InitializeService 方法加载数据时,一切正常。 ComboBox 填充ObservableCollection 的数据,匹配值由ExecutionTimeId 自动选择。 当我尝试“重新初始化”服务时,我再次调用相同的方法,但 SelectedValue 绑定不再起作用。我在调试器中检查了这些属性的值,但在此方法中再次正确设置了它们。我在这里做错了什么?一些样本:

正确显示首次加载:

不正确的显示秒加载:

【问题讨论】:

  • 你是否在没有提升 PropertyChanged 的​​情况下替换了 ObservableCollection?
  • @EdPlunkett 我只清除并重新填充此集合。通常它应该在发生更改时自动引发 PropertyChange,还是我错了?
  • 但是 ItemsSource 绑定确实有效,即 ComboBox 有一个执行时间列表?这个新列表是否包含先前选择的值,即具有相同 Id 的 ExecutionTime?
  • 在运行时使用 Snoop 检查您的 DataContext 和绑定
  • @Clemens 是的,你的两个建议都有。如果打开ComboBox,项目仍然存在。好像只有自动选择失败了

标签: c# wpf xaml data-binding


【解决方案1】:

TableConfigurationLoader 是一个依赖属性。这意味着很多事情,但其中之一是,当您将TableConfigurationLoader 的值更改为Setting 的不同实例时,会引发一个事件,并且此Binding 处理该事件并更新SelectedValue on组合框:

SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}"

但是,Setting.ExecutionTimeId 不是依赖属性。这是一个常规的 .NET CLR 属性,当它的值发生变化时它不会通知任何人。因此,如果您更改已经存在于TableConfigurationLoader 中的相同旧SettingExecutionTimeId 属性,那么没人知道并且什么也不会发生。

由于Setting 不是一个控件类,您并不特别需要或希望它的属性是依赖属性。相反,您可以将其视为视图模型。在实现方面,真正的视图模型是任何实现INotifyPropertyChanged 的类。随着对Setting 的更改如下所示,如果我正确理解您的问题,我认为绑定应该按您的预期工作。我已经更改了IsEnabled,所以它也会提高PropertyChanged;您可能实际上并不需要它,但它是说明性的。

您可能需要对您的 ExecutionTime 类执行相同的操作。

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] String propName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}

public class Setting : ViewModelBase
{
    public string Name { get; set; }

    #region IsEnabled Property
    private bool _isEnabled = false;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            if (value != _isEnabled)
            {
                _isEnabled = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion IsEnabled Property

    #region ExecutionTimeId
    private int _executionTimeId = 0;
    public int ExecutionTimeId
    {
        get { return _executionTimeId; }
        set
        {
            if (value != _executionTimeId)
            {
                _executionTimeId = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion ExecutionTimeId
}

WPF 中有三种(ish)机制用于通知属性已更改的事物,如果您希望事物正确更新,则需要以某种方式使用其中的一种:

  • 依赖对象的依赖属性:对于控件的属性
  • INotifyPropertyChanged: 对于视图模型的属性
  • INotifyCollectionChanged:用于收藏。

    当你为它分配一个新的集合实例时,集合属性也应该引发INotifyPropertyChanged.PropertyChanged。集合的给定实例将在其内容更改时处理引发其自己的事件。

    ObservableCollection&lt;T&gt;ReadOnlyObservableCollection&lt;T&gt; 实现 INotifyCollectionChanged 所以你不必这样做;正确实施它是一个很大的麻烦,所以你真的不想去那里。

【讨论】:

  • 感谢您的帮助!我会试试的
【解决方案2】:

在引用实际对象之前创建Setting 的新实例解决了我的问题。如果我只是“覆盖”该属性的现有实例,似乎对Setting 的特定属性的引用丢失了:

var settings = restService.GetSettings();
this.UCSettings.ExecutionTimes.Clear();
settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
this.UCSettings.TableConfigurationLoader = new Setting();
this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");

【讨论】:

  • 您是说TableConfigurationLoader.ExecutionTimeId 的值发生了变化,但除非您先将TableConfigurationLoader 设置为不同的对象实例,否则UI 无法识别这一事实?
  • 是的,确实如此
【解决方案3】:

尝试像这样将 UpdateSourceTrigger=PropertyChanged 添加到绑定中

<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
<ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, UpdateSourceTrigger=PropertyChanged,Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>

【讨论】:

  • 感谢您的回答,但并没有解决问题。
  • 因为UpdateSourceTrigger=PropertyChangedSelectedValue 绑定没有影响。不敢相信有人对此表示赞同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多