繁体   English   中英

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>

主窗口类:

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类

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);
            }
        }

    }
}

首先,您做错了很多事情。

您不应该使用窗口,因为它是自己的数据上下文,应该设置一个视图模型。

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

您的源代码似乎是“ 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);
        }
    }
}

并将此视图模型设置为窗口的datacontext。

this.DataContext = new MainWindowViewModel();

然后在您的xaml中,您应该这样绑定到viewmodel

<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 (窗口)。 您在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类,以实际通知更改:

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>

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

但是,如果您将“源”分配给文本块数据上下文,那么我们可以获取“ 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>

我们为您的第二个Textblock命名为“ TextBlock2”,并从绑定中删除了“ Datacontext”部分。

然后,我们将“源”对象的创建从按钮事件移到了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";
}

但是,还有更多细节。 您的类“ Source”确实实现了“ INotifyPropertyChanged”,但从未“使用”它。 我的意思是,当您为属性“ HeaderText2”分配值时,您实际上从未“通知” UI发生了某些更改,因此UI不会获取新值。 考虑下面的代码:

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

    ...
}

因此,让我们看一下对属性“ HeaderText2”所做的工作。 每当“ HeaderText2”获得分配的值时,它将首先将该值保存在privat属性中(以便我们以后可以从中读取)。 但是除此之外,我们还使用属性名称调用“ OnPropertyChanged”方法。 该方法将依次检查是否有人“监听”了我们的“ PropertyChanged”事件(并且由于我们对当前对象进行了数据绑定,因此有人正在监听),并创建了一个新事件。

现在,我们已经为您的文本块分配了一个带有“ HeaderText2”路径的数据源,当我们在数据源上更新“ HeaderText2”并且在按钮click事件上更新“ HeaderText2”时,我们将通知所有侦听器。

编码愉快!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM