wpf viewmodel和界面双向通知

通知模型(Model)或视图模型(ViewModel) 这两个有什么区别?分别给我代码例子


在MVVM(Model-View-ViewModel)架构中,ModelViewModel扮演不同的角色:

  • Model表示应用程序的数据域,负责业务逻辑和数据访问。它通常不直接与用户界面交互。
  • ViewModel作为ModelView之间的桥梁,包含展示逻辑和命令,以及用户交互逻辑。它通常包括对Model的引用,并实现INotifyPropertyChanged接口,以便在数据发生变化时更新视图。

以下是ModelViewModel的简单代码例子:

Model(模型)

// 一个简单的Model,不实现INotifyPropertyChanged,因为它不需要直接更新UI
public class User
{
    public string Name { get; set; }
    public int Age { get; set; }

    // 其他业务逻辑和数据访问方法
}

ViewModel(视图模型)

// 实现INotifyPropertyChanged接口的ViewModel
public class UserViewModel : INotifyPropertyChanged
{
    private User user;

    public string Name
    {
        get { return user.Name; }
        set
        {
            if (user.Name != value)
            {
                user.Name = value;
                OnPropertyChanged();
            }
        }
    }

    public int Age
    {
        get { return user.Age; }
        set
        {
            if (user.Age != value)
            {
                user.Age = value;
                OnPropertyChanged();
            }
        }
    }

    public UserViewModel(User user)
    {
        this.user = user;
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

在这个例子中,User类是一个简单的数据模型,包含NameAge两个属性。它代表应用程序的数据层,负责定义数据属性和业务逻辑。

UserViewModel类则是视图模型,它包含了一个User实例。视图模型通过暴露NameAge属性,并在它们的setter中调用OnPropertyChanged方法,将User模型的改变通知给界面。这样,当视图(UI)中的数据变化时,这些变化会反映到模型实例中,同时如果模型属性在代码中被修改,UI也会得到更新。这就是双向数据绑定。

通常,ViewModel中还会包含命令(比如ICommand实现),用于响应用户交互,如按钮点击等。这些命令可能会改变模型的状态或触发业务逻辑。这部分逻辑被封装在视图模型中,而不是在模型本身,以保持模型的独立性和重用性。

根据上面的后端代码,xaml代码实现界面修改修改model数据


根据提供的后端代码,我们可以在XAML中设置绑定,以便用户界面可以修改Model数据。假设我们的ModelUser类,ViewModelUserViewModel类。

首先,确保MainWindowDataContext设置为我们的UserViewModel

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <!-- 这里假设你已经在XAML的Code-behind中创建了UserViewModel的实例并赋值给this.DataContext -->
    </Window.DataContext>

    <!-- ... -->
</Window>

接下来,在XAML中,我们添加两个TextBox控件,分别绑定到ViewModelNameAge属性:

<Grid>
    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Label Content="Name:"/>
        <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <Label Content="Age:"/>
        <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
    </StackPanel>
</Grid>

在上面的代码中:

  • Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" 表示TextBox的文本将与UserViewModel中的Name属性进行双向绑定。UpdateSourceTrigger=PropertyChanged表示每当文本框的内容发生变化时,ViewModel中的Name属性也会随之更新。
  • 同样的,Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"将年龄输入框与ViewModel中的Age属性进行双向绑定。

在这种设置下,用户界面上对TextBox的任何更改都会即时更新到UserViewModel中,进而通过它的setter方法更新到User模型中。同样地,如果User模型中的数据在后端代码中被改变,它也会通过INotifyPropertyChanged接口通知界面,使得界面上的显示也会更新。

注意:实际应用中,你需要在你的窗口的代码后面(code-behind)创建UserViewModel的实例,并将它设为窗口的DataContext。例如:

public MainWindow()
{
    InitializeComponent();
    User user = new User() { Name = "Alice", Age = 30 };
    this.DataContext = new UserViewModel(user);
}

确保你的UserUserViewModel类在同一个命名空间中,或者你已经在XAML文件的顶部通过xmlns:local指令引入了正确的命名空间。

有一个问题,Mode=TwoWay设置双向,viewmodel里的nodification是不是就工程重复?


实际上,Mode=TwoWay的设置和ViewModel中的通知(notification)是双向绑定机制中两个互补的部分:

  1. 从界面到模型(View -> ViewModel/Model):

    • Mode=TwoWay的绑定确保了界面元素(例如TextBox)的更改会被推送到数据模型中去。在ViewModel的属性设置器(setter)中,这些更改会被接收并用来更新模型。
  2. 从模型到界面(ViewModel/Model -> View):

    • 当数据模型的状态在代码中被改变时(可能是来自另一个绑定的界面元素的更改,或是后台逻辑的结果),INotifyPropertyChanged接口的实现允许ViewModel通知界面更新。PropertyChanged事件的触发会导致绑定到这个特定属性的界面元素更新其显示。

这并不是冗余的,而是确保了无论数据变化是由用户界面引起的还是由代码逻辑引起的,所有的变化都会同步。也就是说:

  • 当用户在TextBox中输入文字时,TextBoxText属性会发生变化,这个变化通过绑定推送到ViewModel的对应属性。如果ViewModel的属性设置器中有附加的逻辑,它会执行,并且通过INotifyPropertyChanged通知界面更新。
  • 如果有一些非界面逻辑改变了ViewModel的属性(比如,来自网络的数据更新),ViewModel将通过PropertyChanged事件通知界面元素更新其显示,即使这个更改是在界面之外发生的。

Mode=TwoWay确保了这个通信是双向的,而INotifyPropertyChanged接口则确保了界面始终能够显示最新的数据状态。这就是为什么在MVVM模式中ViewModel通常会实现这个接口。

Mode=TwoWay确保了这个通信是双向的 和Mode=OneWay什么区别


在WPF的数据绑定机制中,Mode=TwoWayMode=OneWay的主要区别在于数据更新的方向:

  • Mode=TwoWay:数据绑定是双向的。这意味着任何一端的更改(无论是源对象还是目标对象)都会更新另一端。例如,如果你将一个TextBoxText属性绑定到一个模型的Name属性,并设置为TwoWay模式,那么当TextBox中的文本更改时,Name属性会更新,反之亦然。

  • Mode=OneWay:数据绑定只是单向的,只从源对象更新到目标对象。源对象的更改会影响目标对象,但目标对象的更改不会影响源对象。如果你将一个TextBlockText属性绑定到模型的Name属性,并设置为OneWay模式,那么只有当Name属性更改时,TextBlock的显示文本才会更新。如果TextBlock的内容发生变化(虽然TextBlock通常不用于输入),它不会影响Name属性。

通常,对于用户输入控件(如TextBox),如果你希望模型在用户输入时被更新,你会使用Mode=TwoWay。而对于只用于显示数据的控件(如TextBlock),你会使用Mode=OneWay,因为用户不会更改显示数据,你只需要从模型获取数据来显示。

下面是一个简单的示例来说明这两种绑定模式:

<!-- TwoWay绑定 -->
<TextBox Text="{Binding Name, Mode=TwoWay}"/>

<!-- OneWay绑定 -->
<TextBlock Text="{Binding Name, Mode=OneWay}"/>

默认情况下,WPF中某些属性的绑定模式如下:

  • TextBox.Text默认是TwoWay绑定。
  • TextBlock.Text默认是OneWay绑定。

在设计用户界面和交互时,选择合适的绑定模式是非常重要的,以确保程序逻辑的正确性和高效性。

哦,OneWay是在实现INotifyPropertyChanged的功能上,才能有viewmodel属性修改,界面才会改变。TwoWay才是有界面修改更改viewmodel的属性?


是的,您的理解是正确的。在WPF数据绑定中,OneWayTwoWay绑定模式与INotifyPropertyChanged接口的使用密切相关,它们定义了数据更改时更新的方向:

  • OneWay绑定:数据从源(如ViewModel的属性)流向目标(如View中的UI元素)。这种模式下,只有当源对象的属性发生变化时,目标UI元素才会更新。要使OneWay绑定工作,源对象的属性更改必须触发PropertyChanged事件。这通常用于只显示数据的UI元素,例如标签(Label)或文本块(TextBlock)。

  • TwoWay绑定:数据在源和目标之间双向流动。这意味着当UI元素(目标)上的数据更改时(例如用户编辑了TextBox中的文本),这个更改会被推回到源对象(ViewModel的属性),并且如果源对象的属性更改,UI元素也会更新。TwoWay绑定通常用于用户可以交互和修改数据的UI元素,例如文本框(TextBox)。

这里有一个示例:

<!-- 使用OneWay绑定 -->
<TextBlock Text="{Binding UserName, Mode=OneWay}"/>

<!-- 使用TwoWay绑定 -->
<TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

在上述示例中:

  • TextBlock 使用OneWay绑定:这意味着如果ViewModel中的UserName属性更改,TextBlock将显示新的用户名,但如果TextBlockText在某处被代码更改,这不会影响ViewModel中的UserName属性。
  • TextBox 使用TwoWay绑定:这意味着不仅当ViewModel中的UserName属性更改时TextBox会更新显示,而且当用户在TextBox中输入新的用户名时,ViewModel中的UserName属性也会更新。这是通过属性的setter和PropertyChanged事件相结合实现的。

在使用TwoWay绑定模式时,通常也会设置UpdateSourceTrigger属性。这个属性定义了更改推回源的时机。对于TextBox控件,默认的UpdateSourceTriggerLostFocus,这意味着当TextBox失去焦点时源属性才会更新。设置为PropertyChanged意味着每当TextBox中的文本更改时,无论焦点如何,源属性都会立即更新。

twoway会不会修改无限循环通知修改?


在正常情况下,使用TwoWay绑定不会导致无限循环。WPF的数据绑定机制被设计为能够处理属性变更通知而不引入循环。当你通过UI更改一个属性,它会更新绑定的ViewModel中的属性,该属性的setter会调用OnPropertyChanged方法通知UI属性已更改。由于绑定的更新通常是条件性的,只有在新值和旧值不相等时才进行更新,这阻止了潜在的无限循环。

例如,看下面的属性setter

private string name;
public string Name
{
    get { return name; }
    set
    {
        if (name != value)
        {
            name = value;
            OnPropertyChanged(nameof(Name));
        }
    }
}

在这里,OnPropertyChanged只有在name的当前值与新值不相同的情况下才会被调用。这就防止了无限循环,因为当新值和当前值相等时,不会发出属性更改通知,UI也就不会尝试再次更新这个值。

然而,如果setter实现有误,例如忽略了值是否真的发生变化,或者在某些情况下错误地更改了另一个属性,它仍然可能导致无限循环:

public string Name
{
    get { return name; }
    set
    {
        name = value; // 这里没有检查值是否相等
        OnPropertyChanged(nameof(Name)); // 这将无条件地触发PropertyChanged事件
    }
}

上面的代码无论name值是否发生了变化,都会触发PropertyChanged事件。如果UI自动响应这个事件去更新name,那么就会形成一个循环。

确保你的属性setter总是检查值是否真的变化了,并且只有在值变化时才触发PropertyChanged事件,这样可以防止无限循环。

哦,也就是属性修改一次,通知界面界面修改,如果界面是源值修改通知,那么会通知绑定viewmodel属性,这时属性手动通知,再发给界面,界面的值没有修改,就不会发通知给viewmodel了吧?


是的,您描述的过程基本上正确了解了TwoWay绑定和INotifyPropertyChanged机制如何合作以避免无限循环。

具体来说,流程如下:

  1. 用户界面更改:当用户在界面上更改了一个绑定到ViewModel属性的控件(如TextBox)的值时,这个新值会被推送到ViewModel的相应属性。

  2. 属性更新:在ViewModel中,对应的属性的setter被调用。在这个setter中,通常会有一个检查来判断新值是否真的和旧值不同。

  3. 通知变化:如果值确实改变了,属性的setter会更新内部变量,并触发PropertyChanged事件,通知界面属性已更改。

  4. 界面响应变化:界面接收到PropertyChanged事件后,会更新显示该属性的界面元素。如果界面元素的显示已经与新的属性值相符,则不会重新触发数据更新回ViewModel

这个过程保证了数据在ViewModel和界面之间同步,而不会引起无限循环。PropertyChanged事件确保只有在数据实际改变时才通知界面更新,而界面上的控件(如果已正确实现)在接收到与其当前值相同的数据时不应再触发更改。这样的设计是数据绑定框架的一部分,目的就是为了有效地管理数据流和更新,避免因为反复无意义的数据推送和更新而导致性能问题或逻辑错误。

如果界面元素的显示已经与新的属性值相符,则不会重新触发数据更新回ViewModel。 这段有代码的具体实现细节吗?


在WPF中,数据绑定框架内部处理确保不会因为值未变而触发无谓的更新,是通过数据绑定机制的一部分实现的。这里面涉及的具体细节通常是透明的,即不需要开发者额外编码来控制。但理解其背后的基本概念是有帮助的。

WPF 数据绑定和更新机制

在WPF中,数据绑定系统使用Binding对象来连接数据源的属性与界面元素。当绑定设置为TwoWay时,以下是更新过程的简化版:

  1. 源值更新: 当用户界面(如TextBox)的值改变,且绑定的UpdateSourceTrigger设置为默认(对于TextBox来说通常是LostFocus)或PropertyChanged时,绑定机制会读取控件的新值,并尝试将其写回到绑定的数据源属性。

  2. 调用属性的Setter: 数据源(通常是ViewModel的某个属性)的setter被调用,如果新值与当前值相同,则可以在setter内部直接返回,不做任何处理:

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name == value) return;  // 如果新值和旧值相同,则不进行后续操作
            _name = value;
            OnPropertyChanged(nameof(Name));
        }
    }
    

    这种检查确保只有在实际需要更新时才会触发PropertyChanged事件。

  3. 触发PropertyChanged: 如果值确实发生了变化,则触发PropertyChanged事件,这会通知界面组件更新显示的值。

  4. 界面响应: 界面元素收到PropertyChanged事件后,会检查事件中指示的属性是否与自己绑定的属性相同,如果不相同,则更新其显示的值。如果界面元素的新值与界面当前显示的值相同,这通常由界面框架负责检查,然后不会进行无谓的DOM更新或重绘。

这一流程是高度优化的。WPF和其他现代UI框架(如Xamarin、UWP)设计有很多内部机制来最小化不必要的更新,以提高性能和响应速度。

代码示例

虽然大部分细节是自动处理的,您可以通过如下方式编写代码来避免无谓的更新:

public class MyViewModel : INotifyPropertyChanged
{
    private int _myProperty;
    public int MyProperty
    {
        get { return _myProperty; }
        set
        {
            if (value != _myProperty)
            {
                _myProperty = value;
                OnPropertyChanged(nameof(MyProperty));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

在这个例子中,PropertyChanged只有在_myProperty的值真正改变时才会触发。这是防止无限循环的一种常见和有效的模式。

总之,通过合理使用属性检查和慎重触发PropertyChanged事件,可以有效避免无限循环及无谓的性能消耗。

UpdateSourceTrigger几种方式


在WPF中,UpdateSourceTrigger属性定义了绑定源(通常是ViewModel中的属性)何时接收来自绑定目标(界面元素,如TextBox)的更新。这个属性主要用在TwoWayOneWayToSource绑定模式下,指定用户界面元素值的变化何时传递回绑定的数据源。以下是UpdateSourceTrigger的几种常见设置方式:

1. Default

Default是大多数绑定属性的默认触发器,具体行为取决于绑定的属性。例如,TextBox.Text属性的默认触发器是LostFocus,而大多数其他属性的默认触发器是PropertyChanged。这意味着,如果你没有显式设置UpdateSourceTrigger,它将采用默认行为:

<TextBox Text="{Binding Path=UserName, Mode=TwoWay}"/>
<!-- 默认是 LostFocus -->

2. PropertyChanged

当设置为PropertyChanged时,每当绑定的界面元素(如TextBoxText属性)变化时,即时更新数据源,不需要等到控件失去焦点:

<TextBox Text="{Binding Path=UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

这种方式在需要实时搜索或实时数据处理的场景中非常有用。

3. LostFocus

当设置为LostFocus时,只有当绑定的界面元素失去焦点时,才会更新数据源。这是TextBox.Text属性的默认行为,有助于避免在用户仍在输入时频繁更新数据源:

<TextBox Text="{Binding Path=UserName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
<!-- 通常不需要显式设置,因为这是默认行为 -->

4. Explicit

Explicit触发器需要开发者手动调用BindingExpression.UpdateSource方法来更新数据源。这种方式允许开发者完全控制何时更新数据源,通常用在用户完成一系列输入步骤后,点击“保存”按钮时:

<TextBox Name="MyTextBox" Text="{Binding Path=UserName, Mode=TwoWay, UpdateSourceTrigger=Explicit}"/>
<Button Content="Update" Click="Update_Click"/>
private void Update_Click(object sender, RoutedEventArgs e)
{
    BindingExpression be = MyTextBox.GetBindingExpression(TextBox.TextProperty);
    be.UpdateSource();
}

这种方式在需要用户确认输入或需要批量更新数据的场景中非常有用,因为它可以防止不必要的数据更新和潜在的性能问题。

总结来说,UpdateSourceTrigger的选择依赖于具体的应用场景和需求,它为开发者提供了灵活的数据更新策略,使得可以根据用户交互的特点和性能考虑来优化数据流。

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金士顿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值