WPF:绑定更新时,绑定丢失 [英] WPF: Binding is lost when bindings are updated

查看:67
本文介绍了WPF:绑定更新时,绑定丢失的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a WPF-Application for controlling a WCF-RESTful service, i.e. for starting, initializing and stopping it. Therefore I have a MainWindow UI which contains a UserControl to configure settings. When I initialize my service, some data is loaded into DependencyProperties and ObservableCollections to display it in the GUI. Here is the part of the method where I update these settings:

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 的某些绑定中使用:

In the Code-Designer (UserControlSettings.xaml.cs) these properties are used in some bindings for a 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 绑定不再起作用。我在调试器中检查了这些属性的值,但是再次在此方法中正确设置了它们。我在这里做错了什么?一些示例:

When I first load in the data with the InitializeService method, everything works fine. The ComboBox is filled with the data of the ObservableCollection and the matching value is selected automatically by the ExecutionTimeId. When I try to "reinitialize" the service, I call the same method again, but the SelectedValue binding does not work anymore. I checked the values of these properties in the debugger, but they are set correctly in this method again. What am I doing wrong here? Some samples:

正确显示第一次加载:

Correct display first load:

不正确的显示秒加载:

Incorrect display seconds load:

推荐答案

TableConfigurationLoader 是依赖项属性。这意味着很多事情,但是其中之一是,当您将 TableConfigurationLoader 的值更改为 Setting ,将引发一个事件,此 Binding 处理该事件并在组合框上更新 SelectedValue

TableConfigurationLoader is a dependency property. That means a lot of things, but one of them is that when you change the value of TableConfigurationLoader to a different instance of Setting, an event is raised, and this Binding handles that event and updates SelectedValue on the combo box:

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

但是, Setting.ExecutionTimeId 不是依赖项属性。这是.NET CLR的常规属性,其值更改时不会通知任何人。因此,如果您更改与 TableConfigurationLoader中相同的旧 Setting ExecutionTimeId 属性, code>,没人知道,什么也没有发生。

However, Setting.ExecutionTimeId isn't a dependency property. It's a regular .NET CLR property, which doesn't notify anybody of anything when its value changes. So if you change the ExecutionTimeId property of the same old Setting that's already in TableConfigurationLoader, nobody knows and nothing happens.

由于设置不是控件类,因此您并不需要或不希望其属性为依赖项属性。相反,您可以将其视为视图模型。在实现方面,所有viewmodel实际上就是实现 INotifyPropertyChanged 的任何类。通过对下面显示的 Setting 进行更改,如果我正确理解了您的问题,我认为绑定应该可以按您期望的那样工作。我更改了 IsEnabled ,因此它也会提高 PropertyChanged ;您可能实际上并不需要,但这只是说明性的。

Since Setting is not a control class, you don't particularly need or want its properties to be dependency properties. Instead, you can treat it as a viewmodel. In implementation terms, all a viewmodel really is, is any class that implements INotifyPropertyChanged. With changes to Setting shown below, I think the binding should work as you expect, if I correctly understand your problem. I've changed IsEnabled so it will raise PropertyChanged as well; you may not actually need that, but it's illustrative.

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

You may need to do the same with your ExecutionTime class.

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)机制用于通知事物属性已更改,如果您希望事物正确更新,则需要以某种方式使用一种或另一种:

There are three (ish) mechanisms in WPF for notifying things that properties have changed, and you need to be using one or another somehow if you want things to update correctly:


  • 依赖项对象的依赖项属性:对于控件的属性

  • INotifyPropertyChanged :视图模型的属性

  • INotifyCollectionChanged :用于集合。

  • Dependency properties of dependency objects: For properties of controls
  • INotifyPropertyChanged: For properties of viewmodels
  • INotifyCollectionChanged: For collections.

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

A collection property should also raise INotifyPropertyChanged.PropertyChanged when you assign a new collection instance to it. A given instance of the collection will handle raising its own events when its contents change.

ObservableCollection< T> ReadOnlyObservableCollection< T> 实现 INotifyCollectionChanged ,因此您不必这样做;正确实施该解决方案很麻烦,因此您真的不想去那里。

ObservableCollection<T> and ReadOnlyObservableCollection<T> implement INotifyCollectionChanged so you don't have to; it's a big hassle to implement that one properly so you really don't want to go there.

这篇关于WPF:绑定更新时,绑定丢失的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆