WPF ~ 绑定和 INotifyPropertyChanged 问题

WPF ~ Trouble with Binding & INotifyPropertyChanged

WPF n00bie 在这里,试图让他的 UI 正常工作。

所以我做了这个测试例子。绑定到 HeaderText1 的文本块在应用程序启动时正确更改,但绑定到 HeaderText2 的文本块在单击按钮后不会更新。

我做错了什么?提前致谢!!

<Window x:Class="DataBinding.DataContextSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataContextSample" Height="142.596" Width="310">
    <StackPanel Margin="15">
        <WrapPanel>
            <TextBlock Text="Window title:  " />
            <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
            <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
        </WrapPanel>
        <TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
        <TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock>
    </StackPanel>
</Window>

主要windowclass:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;


namespace DataBinding
{
    public partial class DataContextSample : Window
    {
        public string HeaderText { set; get; }



        public DataContextSample()
        {
            HeaderText = "YES";


            InitializeComponent();
            this.DataContext = this;

        }

        private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
        {

            BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
            binding.UpdateSource();

            Source source = new Source();
            source.HeaderText2 = "YES2";
        }
    }
}

和 INotifyPropertyChanged class

using System.ComponentModel;

namespace DataBinding
{
    public class Source : INotifyPropertyChanged
    {
        public string HeaderText2 { set; get; }

        public event PropertyChangedEventHandler PropertyChanged;


        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

    }
}

首先你做错了很多事情。

你不应该使用 window 因为它有自己的数据上下文,你应该有一个你设置的视图模型。

您不应在视图中使用事件处理程序来操纵视图模型。您应该将按钮绑定到命令。

您的来源似乎是 "viewmodel",考虑将其重命名为 MainWindowViewModel(为清楚起见),然后执行此操作。

public class MainWindowViewModel : INotifyPropertyChanged
{
    private string headerText;
    private string headerText2;
    private ICommand updateHeaderText2;

    public string HeaderText
    {
        set
        {
            return this.headerText;
        }
        get 
        {
            this.headerText = value;

            // Actually raise the event when property changes
            this.OnPropertyChanged("HeaderText"); 
        }
    }

    public string HeaderText2
    {
        set
        {
            return this.headerText2;
        }
        get 
        {
            this.headerText2 = value;

            // Actually raise the event when property changes
            this.OnPropertyChanged("HeaderText2");
        }
    }

    public ICommand UpdateHeaderText2
    {
        get 
        {
            // Google some implementation for ICommand and add the MyCommand class to your solution.
            return new MyCommand (() => this.HeaderText2 = "YES2");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

并将此视图模型设置为您 window 的数据上下文。

this.DataContext = new MainWindowViewModel();

然后在您的 xaml 中,您应该这样绑定到视图模型

<Window x:Class="DataBinding.DataContextSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataContextSample" Height="142.596" Width="310">
    <StackPanel Margin="15">
        <WrapPanel>
            <TextBlock Text="Window title:  " />
            <!-- Not sure what this binding is? -->
            <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
            <Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button>
        </WrapPanel>
        <TextBlock Text="{Binding HeaderText}"></TextBlock>
        <TextBlock Text="{Binding HeaderText2}"></TextBlock>
    </StackPanel>
</Window>

您将 DataContext 设置为 this(window)。您在 DataContext 中没有名为 HeaderText2 的 属性,因此第二个绑定将不起作用。

我会这样做(无需过多更改您的代码,实际上我会采用适当的 MVVM 方法):

public partial class DataContextSample : Window
{
    public Source Source { get; set; }
    public string HeaderText { set; get; }

    public MainWindow()
    {
        InitializeComponent();

        HeaderText = "YES";
        Source = new Source { HeaderText2 = "YES" };
        DataContext = this;
    }

    private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
    {
        BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
        if (binding != null)
        {
            binding.UpdateSource();
        }

        Source.HeaderText2 = "YES2";
    }
}

我添加了一个名为 Source 的新 属性,其类型为 Source。在构造函数中将其初始 HeaderText2 设置为相同的 "YES",然后在单击按钮时将其更改为 "YES2"

您还必须更改 Source class,才能实际通知更改:

public class Source : INotifyPropertyChanged
{
    private string _headerText2;

    public string HeaderText2
    {
        get { return _headerText2; }
        set
        {
            _headerText2 = value;
            OnPropertyChanged("HeaderText2");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

然后在你的 XAML:

<StackPanel Margin="15">
    <WrapPanel>
        <TextBlock Text="Window title:  " />
        <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
        <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
    </WrapPanel>
    <TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
    <TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock>
</StackPanel>

您的代码存在一些问题。 首先,您永远不会将 "Source" 分配给数据上下文,因此您的第二个 TextBlock 无法找到 "HeaderText2".

的值

但是,如果您将 "Source" 分配给文本块数据上下文,那么我们可以获取 "HeaderText2" 的值。考虑下面的代码

<Window x:Class="DataBinding.DataContextSample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="DataContextSample" Height="142.596" Width="310">
    <StackPanel Margin="15">
        <WrapPanel>
            <TextBlock Text="Window title:  " />
            <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
            <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
        </WrapPanel>
        <TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
        <TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock>
    </StackPanel>
</Window>

我们已经为您的第二个文本块命名,"TextBlock2" 并且还从您的绑定中删除了 "Datacontext" 部分。

然后我们将 "Source" 对象的创建从按钮事件移至 windows 构造函数(无需每次单击按钮时都创建一个新的构造函数)要做的就是更新一个属性)

public partial class DataContextSample : Window
{
    public string HeaderText { set; get; }
    private Source source { get; set; }

    public DataContextSample()
    {
        ...

        source = new Source();
        TextBlock2.DataContext = source;

        ...
    }

    ...
}

然后在您的按钮点击事件中,我们为您的数据绑定 属性 分配一个值 "YES2"。

private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
    ...

    source.HeaderText2 = "YES2";
}

不过还有一个细节。您的 class "Source" 确实实现了 "INotifyPropertyChanged",但它从未 "uses" 它。我的意思是,当您为 属性 "HeaderText2" 分配一个值时,您实际上从未 "notify" UI 某些东西随之改变,因此 UI 不会获取新值。考虑以下代码:

public class Source : INotifyPropertyChanged
{        
    public string HeaderText2 { set
        {
            headerText2 = value;
            OnPropertyChanged("HeaderText2");
        }
        get
        {
            return headerText2;
        }
    }
    string headerText2;

    ...
}

那么让我们来看看我们用 属性 "HeaderText2" 做了什么。每次 "HeaderText2" 被赋值时,它都会先将值保存在私有 属性 中(以便我们稍后可以从中读取)。但除此之外,我们还使用我们的属性名称调用 "OnPropertyChanged" 方法。该方法将依次检查是否有人 "listening" 到我们的 "PropertyChanged" 事件(并且由于我们在当前对象上有数据绑定,有人正在监听),并创建一个新事件。

现在我们已经为您的文本块分配了一个数据源,路径为 "HeaderText2",我们会在更新数据源 "HeaderText2" 时通知所有侦听器,我们正在更新 "HeaderText2"按钮点击事件。

编码愉快!