WPF~绑定&麻烦INotifyPropertyChanged的

时间:2016-03-06 07:19:00

标签: c# wpf xaml data-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);
            }
        }

    }
}

3 个答案:

答案 0 :(得分:2)

首先,你做了很多错事。

您不应该使用窗口作为自己的datacontext,您应该有一个您设置的视图模型。

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

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

并将此viewmodel设置为窗口的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>

答案 1 :(得分:1)

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

我会这样做(不会过多地改变你的代码,实际上我会采用适当的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>

答案 2 :(得分:1)

您的代码存在一些问题。 首先,你永远不会分配你的&#34;来源&#34;对于datacontext,所以你的第二个TextBlock无法找到&#34; HeaderText2&#34;的值。

但是,如果您要分配您的&#34;来源&#34;到textblocks datacontext然后我们可以获取&#34; HeaderText2&#34;的值。请考虑以下代码

<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命名,&#34; TextBlock2&#34;并且还从绑定中删除了&#34; Datacontext&#34; -part。

然后我们移动了你的&#34; Source&#34;从按钮事件到windows构造函数的对象(当我们想要做的只是更新属性时,每次单击按钮时都不需要创建新的对象)

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

    public DataContextSample()
    {
        ...

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

        ...
    }

    ...
}

然后在你的按钮click-event中,我们为你的数据绑定属性赋值为&#34; YES2&#34;。

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

    source.HeaderText2 = "YES2";
}

还有一个细节。你的班级&#34;来源&#34;确实实现&#34; INotifyPropertyChanged&#34;,但它永远不会&#34;使用&#34;它。我的意思是,当你为你的财产分配一个价值&#34; HeaderText2&#34;你实际上从来没有&#34;通知&#34;用户改变了某些内容的UI,因此UI不会获取新值。请考虑以下代码:

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

    ...
}

因此,让我们看看我们对该属性&#34; HeaderText2&#34;所做的事情。每次&#34; HeaderText2&#34;获取一个赋值,它将首先将值保存在privat属性中(以便我们稍后可以读取它)。但除此之外,我们还称之为&#34; OnPropertyChanged&#34;使用我们的Propertys名称的方法。这种方法将反过来检查是否有人正在倾听&#34;到我们的&#34; PropertyChanged&#34; -event(因为我们在当前对象上有一个数据绑定,有人正在侦听),并创建一个新事件。

现在我们已经为你的文本块分配了一个数据源,其中包含一个&#34; HeaderText2&#34;的路径,当我们更新&#34; HeaderText2&#34;时,我们正在通知所有的听众。在数据源上,我们正在更新&#34; HeaderText2&#34;在按钮上单击事件。

快乐的编码!