简体   繁体   English

无法理解 WPF 中的 DataBindings

[英]Trouble understanding DataBindings in WPF

I'm an absolute beginner in WPF and tried to setup a simple DataBinding that updates the text of a TextBlock based on the text value in a TextBox when you click a button.我是 WPF 的绝对初学者,并尝试设置一个简单的 DataBinding,当您单击按钮时,它会根据 TextBox 中的文本值更新 TextBlock 的文本。 I got it to work, but i found two different variants of doing it and in both cases something seems off.我让它工作了,但我发现了两种不同的变体,在这两种情况下似乎都有问题。

Variant 01 works just as it should, but the fact that the target updates the source seems off.变体 01 正常工作,但目标更新源的事实似乎不正确。 In Variant 02 the source updates the target, but the UpdateSourceTrigger is useless and the only thing the mode does is blocking the target from getting updated, so i can do it manually.在 Variant 02 中,源更新目标,但 UpdateSourceTrigger 没有用,模式所做的唯一事情就是阻止目标更新,所以我可以手动完成。

Both variants get the thing done, but in both cases there is something that bothers me and seems off.两种变体都能完成任务,但在这两种情况下,都有一些事情让我感到困扰并且看起来很不对劲。 So what's the 'right' way of doing this and DataBindings in general?那么一般来说,这样做的“正确”方法和 DataBindings 是什么?

C# Code for both variants:两种变体的 C# 代码:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestProject {
    public partial class MainWindow {
        public MainWindow() {
            InitializeComponent();

            // Value propagation: Target(InputFieldVariant01) -> Source(OutputFieldVariant01) 
            Binding binding = new Binding("Text");
            binding.Source = OutputFieldVariant01;
            binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            binding.Mode = BindingMode.OneWayToSource;
            InputFieldVariant01.SetBinding(TextBox.TextProperty, binding);

            // Value propagation: Source(InputFieldVariant02) -> Target(OutputFieldVariant02)
            Binding binding2 = new Binding("Text");
            binding2.Source = InputFieldVariant02;
            binding2.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            binding2.Mode = BindingMode.OneWayToSource; // blocks the updating of the OutputField i guess (?)
            OutputFieldVariant02.SetBinding(TextBlock.TextProperty, binding2);
        }

        private void refreshBtnVariant01_refreshTextBlock(object sender, RoutedEventArgs e) {
            InputFieldVariant01.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
        }

        private void refreshBtnVariant02_refreshTextBlock(object sender, RoutedEventArgs e) {
            OutputFieldVariant02.GetBindingExpression(TextBlock.TextProperty)?.UpdateTarget();
        }
    }
}

and here is my .xaml:这是我的 .xaml:

<Window x:Class="TestProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestProject"
        mc:Ignorable="d"
        Title="MainWindow" Height="120" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        
        <TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Center">Variant 01</TextBlock>
        <TextBox Grid.Row="2" Grid.Column="2" Name="InputFieldVariant01" MinWidth="100"></TextBox>
        <Button Grid.Row="3" Grid.Column="2" Content="Refresh TextBlock" Click="refreshBtnVariant01_refreshTextBlock" Margin="0, 5"></Button>
        <TextBlock Grid.Row="4" Grid.Column="2" Name="OutputFieldVariant01"></TextBlock>
        
        <TextBlock Grid.Row="1" Grid.Column="4" TextAlignment="Center">Variant 02</TextBlock>
        <TextBox Grid.Row="2" Grid.Column="4" Name="InputFieldVariant02" MinWidth="100"></TextBox>
        <Button Grid.Row="3" Grid.Column="4" Content="Refresh TextBlock" Click="refreshBtnVariant02_refreshTextBlock" Margin="0, 5"></Button>
        <TextBlock Grid.Row="4" Grid.Column="4" Name="OutputFieldVariant02"></TextBlock>
        
    </Grid>
</Window>

The way I was taught binding is by implementing the INotifyPropertyChanged interface ( See docs @MSDN ).我学习绑定的方式是实现INotifyPropertyChanged接口( 请参阅文档@MSDN )。 For collections there is an ObservableCollection Type.对于集合,有一个ObservableCollection类型。 You may want to look at MVVM pattern later on but for now just focus on Binding.稍后您可能想查看 MVVM 模式,但现在只关注 Binding。 In XAML there is a special syntax called XAML Markup Extension .在 XAML 中有一种称为XAML 标记扩展的特殊语法。 Here is an example @MSDN .这是一个示例@MSDN

A few points, you are doing all this binding stuff in code.有几点,你在代码中做所有这些绑定的东西。 I suggest looking into MVVM patterns since you are new.我建议您研究 MVVM 模式,因为您是新手。 The basic premise is基本前提是

M = Model - where the underlying data is coming from / stored to
V = View - the user interface presentation context
VM = ViewModel - the glue getting/sending data to ex: sql database, but also making available to the view / end user.

It can typically be found that you create a view model object such as a class and it has public getter/setter properties on it.通常可以发现您创建了一个视图模型对象,例如一个类,并且它具有公共的 getter/setter 属性。 When the object is created within your view constructor and set as the DataContext, all bindings can be done directly within the xaml.当对象在视图构造函数中创建并设置为 DataContext 时,所有绑定都可以直接在 xaml 中完成。 The class does not need to know what the view does with it, the view doesnt need to know how the data is available.类不需要知道视图用它做什么,视图不需要知道数据如何可用。 So you can simplify much of this such as因此,您可以简化其中的大部分内容,例如

namespace TestProject 
{
    public partial class MainWindow 
    {
        public MainWindow() 
        {
            InitializeComponent();
            DataContext = new MyViewModel();
        }
    }
}

Now, in another class such as example indicates above现在,在另一个类中,例如上面的例子

namespace TestProject 
{
    public class MyViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        #endregion INotifyPropertyChanged Members        

        private string _someString = "test string";
        public string SomeString 
        {
            get { return _someString; }
            set
            {
                _someString = value;
                RaisePropertyChanged(nameof(SomeString));
            }
        }

        private int _someNumber = 18;
        public int SomeNumber 
        {
            get { return _someNumber; }
            set
            {
                _someNumber = value;
                RaisePropertyChanged(nameof(SomeNumber));
            }
        }

        public List<SomeTableStructureFromDatabase> ListOfData { get; }

        public MyViewModel()
        {
           ListOfData = SomeMethodToGetDataFromSQLDatabase();
        }
    }
}

So, in the above sample, you can see the PUBLIC get/set with some default values to the string and numeric values.因此,在上面的示例中,您可以看到带有一些默认值的 PUBLIC 获取/设置字符串和数值。 Yes, I also included the INotifyPropertyChanged extension to the class.是的,我还在该类中包含了INotifyPropertyChanged扩展。 You can read more on that later, but allows view components to trigger refresh when things change either internally to the class to refresh the view, or being pushed from the view back to the view model.您可以稍后阅读更多内容,但允许视图组件在内部更改为类以刷新视图或从视图推回视图模型时触发刷新。

Now, how to handle the view.现在,如何处理视图。 As YOU are developing, you just need to know the names of the pieces on the view model that are exposed publicly.随着您的开发,您只需要知道视图模型上公开显示的部分的名称。 Then, in the xaml, identify the bindings directly.然后,在 xaml 中,直接识别绑定。 Again, the view should not know how or where the data parts are coming from, just what they are called and exposed as.同样,视图不应该知道数据部分是如何或从哪里来的,只是它们被称为和公开的内容。 So the VIEW (MainWindow.xaml) might have your entry textblocks/textbox, etc as:所以 VIEW (MainWindow.xaml) 可能有你的输入文本块/文本框等:

<TextBox MinWidth="100" [yes, you can assign grid row/columns as normal]
    Text="{Binding SomeString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />

<TextBox Width="45" MaxLength="4"
    Text="{Binding SomeNumber, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
        

Notice the direct binding of the textboxes.注意文本框的直接绑定。 Since the MainWindow has its DataContext based on the MyViewModel class created in its constructor, the publicly exposed properties are available to bind to.由于 MainWindow 的DataContext基于在其构造函数中创建的MyViewModel类,因此公开的属性可用于绑定。 It can all be handled in the xaml.这一切都可以在 xaml 中处理。 You just need to know what the property is called on the class.您只需要知道在类上调用的属性是什么。 You dont need to explicitly NAME each of the controls so the .CS code has to know the view side name as well.您不需要显式命名每个控件,因此 .CS 代码也必须知道视图端名称。

Also, your context of forcing the mode as ONE WAY TO SOURCE is not going to be as common as you might think.此外,您将模式强制为 ONE WAY TO SOURCE 的上下文不会像您想象的那样普遍。 Think of it this way.这样想吧。 You want to pull data from a database, expose it for being edited and then saved back.您想从数据库中提取数据,将其公开以进行编辑,然后再保存回来。 If one-way to the source, you cant get and push TO the field to show the user.如果单向源,则无法获取并推送到该字段以向用户显示。 So, explicitly stating the mode is one way you might shy away from unless truly needed.因此,除非确实需要,否则明确说明该模式是您可能会回避的一种方式。

Anyhow, hope this might open your mindset to additional reading and understanding of some bindings.无论如何,希望这可以打开您的思维方式,以进一步阅读和理解某些绑定。 Lastly, as I sampled the class with default text string and numeric values, when you do run the form, you should see those values default when the form is presented.最后,当我使用默认文本字符串和数值对类进行采样时,当您运行表单时,您应该会在呈现表单时看到这些默认值。 Also, as a numeric (or even date/time as applicable to DatePicker control), you dont have to worry about data conversion from text entry and making sure numeric or bogus text.此外,作为数字(甚至适用于 DatePicker 控件的日期/时间),您不必担心文本输入的数据转换并确保数字或虚假文本。 The binding will only update the value if it is the proper data type for the property being bound to.只有当它是绑定到的属性的正确数据类型时,绑定才会更新该值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM