【问题标题】:WPF databinding to object's propertyWPF 数据绑定到对象的属性
【发布时间】:2019-01-11 16:02:17
【问题描述】:

我正在尝试使用 WPF 构建一个计算器。我正在使用多个页面,以便我可以练习在页面之间使用数据绑定。但是,我的显示器没有正确绑定。当我点击“0”时,我希望显示器显示“0”。输入数字时显示不更新。

我意识到关于 SO 有许多类似的问题。这些问题与我的不同之处在于我已经在实施 INotifyPropertyChanged,但这并没有解决我的问题。

代码设置

“数字键盘”包含数字按钮 0-9。 “显示”只是顶部的一个大按钮,应该显示点击的数字。

NumberPad.xaml.cs

public partial class NumberPad : Page
{
    Display m_display;

    public NumberPad()
    {
        InitializeComponent();
        m_display = new Display();
        this.DataContext = m_display;
    }

    private void Button0_Click(object sender, RoutedEventArgs e)
    {
        m_display.EquationText = "0";
    }

    private void Button1_Click(object sender, RoutedEventArgs e)
    {
        m_display.EquationText = "1";
    }
 }

Display.xaml.cs

public partial class Display : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string m_equationText;

    public Display()
    {
        InitializeComponent();
        EquationText = "Hi";
    }

    public string EquationText
    {
        get { return m_equationText; }
        set
        {
            m_equationText = value;
            OnPropertyChanged("EquationText");
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Display.xaml

<Page x:Class="WPFCalculator.Display"
    x:Name="_this"
    ....
    Title="Display">

    <Button Content="{Binding ElementName=_this, Path=EquationText, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource buttonStyle}" FontSize="60">

    </Button>
</Page>

预期:我应该能够按 0 或 1 并且其对应的数字会显示在显示屏的按钮中。实际:在整个执行过程中只显示“Hi”。

关于 Display.xaml 的重要说明

我在“ElementName=_this, Path=”中添加了“Hi”显示。如果我只有“Binding EquationText”,它不会显示“Hi”或 0 或 1 或任何东西。我包含此代码是因为它是使 Display 当前显示任何内容的唯一方法。

结论

总而言之,我相信 DataContext 被设置为与实际绑定的不同对象的 EquationText 属性。如何以 NumberPad 可以更新的方式将显示数据绑定到属性?

谢谢!

【问题讨论】:

  • 你不应该练习不同页面之间的数据绑定;相反,您应该练习视图和数据上下文(即视图模型)之间的数据绑定。一个简单的计算器应用程序是学习 MVVM 的完美方式。只有 MVVM 方法才能真正释放 WPF 的力量。
  • @dymanoid,感谢您的回复。我明白你在说什么!我将很快启动另一个测试项目来练习数据绑定,就像它的实际意图一样。

标签: c# wpf xaml data-binding datacontext


【解决方案1】:

让我首先尝试帮助您找到解决问题的方法:

不需要定义:

<Button Content="{Binding ElementName = _this, ....}"></Button>

因为当您在代码隐藏文件中将新创建的显示实例设置为实际窗口实例 (this.DataContext) 的 DataContext 属性时,WPF 很清楚您要提供给窗口的数据位于您的窗口中显示实例设置为 DataContext 属性。 当我们使用 DataBinding 时,WPF 在元素树中分层搜索具有 DataContext 的下一个元素。这意味着,由于您的按钮本身没有 DataContext,WPF 会上升一个级别并查看(因为您在按钮上方没有进一步的分层控件),是否设置了 Window 的 DataContext。由于您的案例中的 DataContext 已经在您的代码隐藏文件中设置,因此 WPF 现在知道您要绑定到来自窗口的 DataContext 的数据。好吧,您所要做的就是绑定到您要显示的属性。你可以这样做:

<Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
 AncestorType=Display}, Path=EquationText}"...>

我建议您使用 MVVM 模式来获得您要解决的练习的一个不错的解决方案(:!

您尝试编写的代码混合了需要明确区分的不同职责。如上图所示,一个窗口应该代表一个视图。这意味着显示框不必是单独的页面。最好使用容器(例如网格)来构建视图。 此外,xaml 文件的代码隐藏文件中不应有任何逻辑。与其使用按钮单击句柄,不如使用 ViewModel 中定义的命令。借助 MVVM,您想要实现的理想状态是您想要完全分解视图和模型。这使您的代码更易于维护且更易于替换。回到您的点击句柄:不建议为每个数字创建回调。这使代码更加混乱,尤其是它引入了冗余,因为在每个回调中几乎都会发生相同的事情。您将字符串分配给同一属性。所以从逻辑上讲,你会拥有 9 个键、9 个回调,因此有 9 个相同的代码。

我对您的建议是查看 MVVM 模式,我相信您的代码会更清晰。

【讨论】:

  • 你也可以试试&lt;Button Content="Binding EquationText}" ...&gt;
  • 不幸的是,显示屏仍然没有响应数字按钮的点击。无论如何,感谢您的详细回复。它非常清晰和有帮助,我想我现在对 MVVM 的了解要多得多:) 在再次使用数据绑定之前,我将优先考虑学习它。
【解决方案2】:

如果没有所有代码,我不能 100% 确定会发生什么,但试试这个:

<Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
 AncestorType=Display}, Path=EquationText}" Style="{StaticResource buttonStyle}" FontSize="60">

【讨论】:

  • 感谢您的回答。我想我知道您使用该解决方案的目的是什么,但不幸的是,输出没有改变。
【解决方案3】:

欢迎使用 S/O,这是一个寻找和提供帮助的优秀免费资源。尽管有很多关于做 MVVM 模式和学习绑定的文章,但有时在事情“点击”到那个 ahhhhhh 时刻之前,你必须有自己的心态。您的视觉样本完全没有问题并且是已知的目标。我在类似的上下文中创建了一个 WPF 窗口。但是,我创建了一个方法来接受所有按钮,因此我不必为每次调用重复创建一个函数。通过查看发送者(传入的按钮对象)并将其作为“按钮”获取并查看其 Content 属性,我根据需要获得了相应的 0、1、2 和 +、-、*、/。对于数字,我只是添加到显示中,对于符号和相等,我把它留给你。但是,由于该值与公共属性相关联(通过 get/set),这就是文本“绑定”的内容,您会看到它正在更新。

现在您将数学函数的逻辑添加到您的内部“_equationText”属性中,您显然会根据需要合并、清除 +、-、*、/ 函数之间的显示并继续您的学习。 HTH。我创建了名为 MyCalculator 的窗口,您几乎可以从那里复制/粘贴并运行。

MyCalculator.xaml

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
            <RowDefinition Height="60" />
        </Grid.RowDefinitions>


        <TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" 
            IsReadOnly="True"
            Text="{Binding DisplayText}"
            Height="28" FontSize="16"
            TextAlignment="Right"
            VerticalAlignment="Center"/>


        <Button Content="7" Grid.Row="2" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="8" Grid.Row="2" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="9" Grid.Row="2" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="+" Grid.Row="2" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="4" Grid.Row="3" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="5" Grid.Row="3" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="6" Grid.Row="3" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="-" Grid.Row="3" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="1" Grid.Row="4" Grid.Column="0"
            Click="AnyButton_Click" />

        <Button Content="2" Grid.Row="4" Grid.Column="1" 
            Click="AnyButton_Click" />

        <Button Content="3" Grid.Row="4" Grid.Column="2" 
            Click="AnyButton_Click" />

        <Button Content="*" Grid.Row="4" Grid.Column="3" 
            Click="AnyButton_Click" />


        <!--<Button Content="1" Grid.Row="5" Grid.Column="0"
            Click="AnyButton_Click" />-->

        <Button Content="0" Grid.Row="5" Grid.Column="1" 
            Click="AnyButton_Click" />

        <!--<Button Content="3" Grid.Row="5" Grid.Column="2" 
            Click="AnyButton_Click" />-->

        <Button Content="/" Grid.Row="5" Grid.Column="3" 
            Click="AnyButton_Click" />


        <Button Content="=" Grid.Row="6" Grid.Column="3" 
            Click="AnyButton_Click" />
    </Grid>
</Window>

MyCalculator.xaml.cs

using System.ComponentModel;
using System.Windows;

namespace StackHelp
{
    /// <summary>
    /// Interaction logic for MyCalculator.xaml
    /// </summary>
    public partial class MyCalculator : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public MyCalculator()
        {
            DataContext = this;
            InitializeComponent();
        }

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string m_equationText;

        private string _displayText = "Hi";
        public string DisplayText
        { get { return _displayText; }
            set { _displayText = value;
                    OnPropertyChanged( "DisplayText" );
            }
        }


        private void AnyButton_Click(object sender, RoutedEventArgs e)
        {
            var whichBtn = sender as System.Windows.Controls.Button;
            if (whichBtn == null)
                return;

            // pre-clear just because it was sample anyhow.
            if (_displayText == "Hi")
                _displayText = "";

            switch( whichBtn.Content )
            {
                case "0":
                case "1":
                case "2":
                case "3":
                case "4":
                case "5":
                case "6":
                case "7":
                case "8":
                case "9":
                    DisplayText += whichBtn.Content;
                    break;

                case "+":
                case "-":
                case "*":
                case "/":
                case "=":
                    // how to handle...  using your computation learning method
                    // then finish by setting the display text with the new string 
                    // represented answer
                    break;

                default:
                    // invalid button
                    break;
            }

        }
    }
}

【讨论】:

  • 非常感谢,这比我处理按钮点击的 hacky 方式要干净得多。谢谢!