在WPF中绑定依赖属性和普通属性

4
我几天前开始学习WPF,并创建了一些测试项目来评估我对所学材料的理解。根据这篇文章,“只有依赖属性才能绑定属性”。为了测试这个,我使用了两种不同的模式:
1. 使用类似MVVM的模式
我创建了Movie模型:
public class MovieModel
{
    private string _movieTitle;
    private string _rating;

    public string MovieTitle
    {
        get { return _movieTitle; }
        set { _movieTitle = value; }
    }

    public string Rating
    {
        get { return _rating; }
        set { _rating = value; }
    }
}

MovieViewModel 视图模型相关:

public class MovieViewModel
{
    MovieModel _movie;

    public MovieViewModel()
    {
        _movie = new MovieModel { MovieTitle = "Inception (Default)" };
    }

    public MovieModel Movie
    {
        get { return _movie; }
        set { _movie = value; }
    }

    public string MovieTitle
    {
        get { return Movie.MovieTitle; }
        set { Movie.MovieTitle = value; }
    }
}

最后在我的主View中,我将DataContext设置为MovieViewModel类的一个实例:

<Window x:Class="MVVMTestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MVVMTestProject.ViewModels"
    Name="MainWindowElement"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:MovieViewModel/>
</Window.DataContext>
<Grid>
    <StackPanel Orientation="Vertical">
        <TextBox Width="150" Text="{Binding Path=MovieTitle}"></TextBox>
    </StackPanel>
</Grid>

当我运行应用程序时,即使ModelViewModel中的属性都不是DependencyProperties,文本框中会显示Inception(Default)。这个方法很好用。第二种尝试是:

2. 在MainView的代码后台中使用属性

在我的主视图的代码后台中,我添加了以下代码:

private string _myText;

public MainWindow()
{
    InitializeComponent();
    MyText = "Some Text";
}

public string MyText
{
    get { return _myText; }
    set { set _myText = value; }
}

然后我改变了我的观点:

<Window x:Class="MVVMTestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MVVMTestProject.ViewModels"
    Name="MainWindowElement"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:MovieViewModel/>
</Window.DataContext>
<Grid>
    <StackPanel Orientation="Vertical">
        <TextBox Width="150" Text="{Binding ElementName=MainWindowElement, Path=MyText}"></TextBox>
    </StackPanel>
</Grid>

但是这并没有起作用。当我运行应用程序时,文本框是空的。所以我将属性更改为依赖属性,奇怪的是,它起作用了。所以我不明白为什么模型和视图模型上的属性可以是常规属性,但是代码后台属性必须是依赖属性才能进行绑定?是因为视图模型设置为视图的数据上下文,解析器足够聪明以认识到这一点,还是有其他原因?
另外一个不太相关的问题:我注意到在几乎每篇关于WPF的文章中,人们都使用本地变量,然后为其定义一个getter和setter,尽管我们都知道在.NET 2.5(我相信?)及以上版本中,我们可以只使用get; set;语法而不必定义本地成员变量。那么这是有特定的原因吗?
提前感谢。

当您调试第二个示例(绑定到代码后台属性时不起作用的示例)时,您是否在输出窗口中看到任何绑定错误? - Jurica Smircic
我几分钟前做了这件事,而且它起作用了。 - Arian Motamedi
有趣的发现...我不能说知道为什么绑定在InitializeComponent()之前设置值时没有被评估。更神秘的是,您没有收到任何绑定错误,这意味着绑定已经被评估并返回了null...但如果您关心,有一些调试绑定的方法:http://blogs.msdn.com/b/wpfsldesigner/archive/2010/06/30/debugging-data-bindings-in-a-wpf-or-silverlight-application.aspx - Jurica Smircic
是的,这很奇怪,我几乎肯定在InitializeComponent()之前放置它会起作用。这让我想,也许XAML代码甚至在InitializeComponent()调用之前就被执行了? - Arian Motamedi
我不认为是这样的。InitializeComponent 只不过是调用了 Application.LoadComponent 并传入一个编译为程序集资源的 XAML 的 URI。你可以在 \obj 文件夹中生成的 .cs 文件中看到这一点。因此,如果没有 LoadComponent,也就没有从 XAML 进行对象初始化。我怀疑 ElementName 作为绑定源与 DataContext 对象的绑定有所不同。 - Jurica Smircic
显示剩余2条评论
3个回答

3
  1. DependencyProperty只在FrameworkElement(DependencyObject)上需要,这是将通过XAML应用{Binding}扩展或通过代码设置绑定的属性,意味着您将用作绑定目标的属性。在您的示例中,只有TextBox.Text需要是DependencyProperty。在第二个示例中,绑定不起作用,因为您没有设置机制来从绑定源发送通知,说明属性值已更改。通常,这是通过实现INotifyPropertyChanged接口并在属性setter中引发PropertyChanged事件来完成的。当您将属性更改为DependencyProperty时,还会获得通知机制,因此属性更改会传播到目标(TextBox)。因此,您可以通过引发PropertyChanged事件而无需DependencyProperty来使其工作。 请注意,除了绑定之外,还有其他需要DependencyProperty的用途(样式、动画...)

  2. PropertyChanged事件可能是使用支持字段(而不是自动属性)实现属性的主要原因,因为您需要在属性setter中使用PropertyChanged事件。


你说:“在你的第二个例子中,绑定没有起作用,因为你没有机制来从绑定源发送通知,表明属性值已经改变。”但是我在我的第一个例子(类似MVVM模式)中也没有这种机制。 - Arian Motamedi
3
有道理,但是你的MVVM模式类似的设计将viewmodel对象设置在xaml中作为DataContext。这会在绑定评估之前发生,因此您会获得正确的数值。如果在窗口显示后更改ViewModel中的属性值,则不会在文本框中看到新值,除非触发PropertyChanged事件。 - Jurica Smircic
这有很好的意义。谢谢您先生! - Arian Motamedi

1

在您的帖子末尾,还有一个不太相关的问题:

我注意到在几乎每篇WPF文章中,人们都使用本地变量,然后为其定义getter和setter,即使我们都知道在.NET 2.5(我相信?)及以上版本中,我们可以只使用get; set;语法而无需定义本地成员变量。那么这是有特定原因吗?

在您的代码中,您给出了以下示例:

private string _movieTitle;

public string MovieTitle
{
    get { return _movieTitle; }
    set { _movieTitle = value; }
}

那段代码与编译器生成的代码完全相同,当你写出以下代码时:
public string MovieTitle { get; set; }

你经常在WPF中看到第一种形式,是因为我们希望WPF绑定在属性值更改时做出反应,为了实现这一点,我们需要通过INotifyPropertyChanged添加更改通知。

有很多方法可以做到这一点,但在现代C#中,您可以编写以下代码:

public class MovieViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _movieTitle;

    public string MovieTitle
    {
        get { return _movieTitle; }
        set
        {
            if (_movieTitle == value)
                return;
            _movieTitle = value;
            OnPropertyChanged();
        }
    }

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

在setter中,我们使用属性的字符串名称MovieTitle来触发PropertyChanged事件。编译器会为我们传递这个字符串名称,因为我们使用了[CallerMemberName]属性,并且默认值为null,所以我们在调用时不必传递任何内容。

0
使用 MVVM Light,您可以绑定属性并触发事件。
    /// <summary>
    /// The <see cref="SelectedPriority" /> property's name.
    /// </summary>
    public const string SelectedPriorityPropertyName = "SelectedPriority";

    private string _selectedPriority = "normalny";

    /// <summary>
    /// Sets and gets the SelectedPriority property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public string SelectedPriority
    {
        get
        {
            return _selectedPriority;
        }

        set
        {
            if (_selectedPriority == value)
            {
                return;
            }

            RaisePropertyChanging(SelectedPriorityPropertyName);
            _selectedPriority = value;
            RaisePropertyChanged(SelectedPriorityPropertyName);
        }
    }

这个类继承了ViewModelBase,其中包含了RaisePropertyChanged和RaisePropertyChanging方法。你可以从http://www.galasoft.ch/mvvm/这个网站下载框架。安装后,你可以使用代码片段mvvminpc来创建可绑定的属性。

你的另一个问题在为什么在WPF中使用INotifyPropertyChanged进行绑定?中得到了回答。

总之,你需要实现RaisePropertyChanged()方法,并在set;get中使用该方法来通知视图有关属性更改的情况。

实现INotifyPropertyChanged - 是否存在更好的方法?


但那并不是我的问题。MVVM是一种模式,所以我不明白为什么我可以在我的ModelView中使用常规属性,但是我必须在代码后台声明一个依赖属性才能将其绑定。 - Arian Motamedi
这不是依赖属性,你需要使用get; set;扩展来引发属性已更改的事件。此事件通知视图属性已更改。你的问题在这里得到了回答:https://dev59.com/l2kv5IYBdhLWcg3wnCA7 - Robert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接