在 WPF 中指定数据绑定

Specifying data bindings in WPF

我有一个带有 3 个文本框的简单 WPF 应用程序,其中 2 个文本框输入数字,第三个文本框显示单击另一个按钮时输入的总和。

我有 WinForms 和 MFC 背景,对我来说,最直观的做法是右键单击文本框,打开它们的属性并指定局部变量以从框中读取数据。比如MFC就有DDX机制来做这个

但是,在 WPF 中,指定绑定的唯一方法似乎是将 XAML 代码直接添加到 App.XAML,如 here on MSDN 所示。有没有一种方法可以创建绑定而无需将其手动编码为 XAML? XAML 编码对我来说似乎有点令人生畏,因为我是新手。

我的 WPF 表单如下:

<Window x:Class="SimpleAdd.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="174,43,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value1}" VerticalAlignment="Top" Width="120"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="174,84,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value2}" VerticalAlignment="Top" Width="120"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="174,127,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value3}" VerticalAlignment="Top" Width="120"/>
        <Button Content="Add" HorizontalAlignment="Left" Margin="393,84,0,0" VerticalAlignment="Top" Width="75" Click="OnAdd"/>
    </Grid>
</Window>

我的C#文件如下:

namespace SimpleAdd
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnAdd(object sender, RoutedEventArgs e)
        {
            dataModel m1 = new dataModel();
            m1.Value3 = m1.Value1 + m1.Value2; // BUG : All Properties are 0 even after updating the boxes.
        }
    }

    public class dataModel
    {
        private int val1, val2, val3;

        public int Value1
        {
            get {return val1;}
            set { val1 = value; }
        }
        public int Value2
        {
            get { return val2; }
            set { val2 = value; }
        }
        public int Value3
        {
            get { return val3; }
            set { val3 = value; }
        }
    }

}

编辑:为 INotifyPropertyChanged

添加实现
namespace SimpleAdd
{
    public abstract class ObservableObject : INotifyPropertyChanged
    {
        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This 
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public virtual void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new Exception(msg);
                else
                    Debug.Fail(msg);
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raises the PropertyChange event for the property specified
        /// </summary>
        /// <param name="propertyName">Property name to update. Is case-sensitive.</param>
        public virtual void RaisePropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);
            OnPropertyChanged(propertyName);
        }

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

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

        #endregion // INotifyPropertyChanged Members
    }

}

从技术上讲,这不在 App.xaml 中(这是 WPF 中的一个特殊文件)。

也就是说,是的你可以做到。您可以像这样在代码中设置绑定:

textBox1.Text = new Binding("SomeProperty");
...

好吧,那真的很烦人所以我们就在 XAML:

<TextBox Text="{Binding SomeProperty}"/>

这两段代码做的事情相同,但是当您进入更高级的绑定时,XAML 语法更易于使用。另外,您的文本来自何处更明显,而不必打开两个文件。

FrameworkElement class 和 FrameworkContentElement class 都公开了一个 SetBinding 方法。如果您绑定的元素继承了这两个 class 中的任何一个,您可以直接调用 SetBinding 方法。 以下示例创建一个名为 class 的 MyData,其中包含一个名为 属性 的 MyDataProperty。

public class MyData : INotifyPropertyChanged
{
private string myDataProperty;

public MyData() { }

public MyData(DateTime dateTime)
{
    myDataProperty = "Last bound time was " + dateTime.ToLongTimeString();
}

public String MyDataProperty
{
    get { return myDataProperty; }
    set
    {
        myDataProperty = value;
        OnPropertyChanged("MyDataProperty");
    }
}

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string info)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(info));
    }
}

}

以下示例演示如何创建绑定对象以设置绑定源。该示例使用 SetBinding 将 myText(一个 TextBlock 控件)的 Text 属性 绑定到 MyDataProperty。

MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");    
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);

您的文本框未更新,因为您尚未在绑定后设置数据源(DataContext 通常)。

当你写

<TextBox Text="{Binding dataModel.Value1}" />

你说的是真的"pull the value for this field from TextBox.DataContext.dataModel.Value1"。如果 TextBox.DataContext 为空,则不会显示任何内容。

DataContext 是自动继承的,所以下面的代码可以工作:

public partial class MainWindow : Window
{
    public dataModel _data { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        _data = new dataModel();
        this.DataContext = _data;
    }

    private void OnAdd(object sender, RoutedEventArgs e)
    {
        _data.Value3 = _data.Value1 + _data.Value2;
    }
}

假设您还更改了 TextBox 绑定以从中删除 dataModel.

<TextBox Text="{Binding Value1}" />

这会将整个表单的 DataContext 设置为 _data 对象,并且在您的 OnAdd 方法中我们可以更新 _data 对象属性以便更新 UI。

我喜欢写一些关于 WPF 初学者的博客,您可能有兴趣查看其中解释这些概念的几篇文章: