【问题标题】:Specifying data bindings in WPF在 WPF 中指定数据绑定
【发布时间】:2015-07-07 21:31:19
【问题描述】:

我有一个带有 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
    }

}

【问题讨论】:

  • 正确的 WPF 方法是将所有 3 个文本框绑定到数据模型上的属性,并在数据模型中确保 Value3 = Value1 + Value2,或者创建一个 IMultiValueConverter,将其传递给将 TextBox1 和 TextBox2 中的值传递给它,并将它们组合起来并返回 TextBox3 的值。 XAML 通常用于这两种情况。如果您没有尝试以正确的 WPF 方式做事,欢迎您使用后面的代码来执行 TextBox3.Text = int.Parse(TextBox1.Text) + int.Parse(TextBox2.Text); 或您想要的任何方式 :)
  • @Rachel 感谢您向我展示了绳索。然而,我还没有到那里。我想学习正确的 WPF 方式,但基于新的实现,我认为我的绑定不起作用,因为我所有的属性只有 0 个值。
  • 老实说,就 UI 的构建方式而言,我仍然发现 WinForms 在我的大脑中更容易一些。但是,如果您还没有尝试过,我建议您深入研究 MVVM - 您肯定会充分利用您的 XAML!
  • @Rintintin 进行转换时,心态肯定会发生变化。你可能会发现this answer about transitioning from WPF to Winforms 很有用:)
  • @Rachel 感谢您分享指向您博客的链接,我不知道数据上下文。现在将阅读它。

标签: c# wpf xaml user-interface data-binding


【解决方案1】:

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

当你写作时

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

您的真正意思是“从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 初学者的博客,您可能有兴趣查看其中解释这些概念的几篇文章:

【讨论】:

  • 非常感谢。我正确地得到了_data.Value3 = _data.Value1 + _data.Value2;。但是data.Value3 不会发布到TextBox3
  • @user2654449 我想问一下你对INotifyPropertyChanged 的实现,但你已经解决了:)
  • 从您的博客中学习 :) 非常感谢
【解决方案2】:

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

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

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

好的,这真的很烦人,所以我们只需在 XAML 中进行:

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

两段代码做同样的事情,但是当您进入更高级的绑定时,XAML 语法更容易使用。此外,您的文本来自哪里更明显,而不是必须打开两个文件。

【讨论】:

  • 我根据您的建议尝试了绑定,但我认为我做错了,因为绑定不起作用。你能找出上面的错误并告诉我正确的实现吗?
  • @user2654449 对于初学者,您永远不会将窗口的DataContext 设置为数据类。此外,如果属性从代码更改时,您需要使用INotifyPropertyChanged 让 UI 知道。
  • 感谢布拉德利。我阅读了INotifyPropertyChanged。我正在为INotifyPropertyChanged 添加代码,但是当关联的value3 更改时,textBox3 仍然不会刷新其内容
  • @user2654449 这不是魔法,你真的必须提出这个事件。尝试在最后添加 RaisePropertyChanged("Value3") 到您的 Value3 设置器。
  • @user2654449 当然可以:OnPropertyChanged("ProductId"); 你的是受保护的,我不确定你的继承层次结构,所以我建议了另一种方法。 usual 实现可以在这里找到(你甚至不需要字符串!)msdn.microsoft.com/en-us/library/…
【解决方案3】:

FrameworkElement 类和 FrameworkContentElement 类都公开了一个 SetBinding 方法。如果您要绑定继承这些类中的任何一个的元素,则可以直接调用 SetBinding 方法。 下面的示例创建一个名为 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 将作为 TextBlock 控件的 myText 的 Text 属性绑定到 MyDataProperty。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-10
    • 1970-01-01
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    相关资源
    最近更新 更多