【问题标题】:View doesn't get updated by Model视图没有被模型更新
【发布时间】:2018-11-22 19:04:18
【问题描述】:

对于这个冗长的问题,我深表歉意,但我希望你能坚持下去。我已尽力包含所有相关代码 sn-ps 以准确解释我想要实现的目标。

我正在尝试将我的移动应用程序转换为 MVVM 设计模式,但在根据模型中发生的更改更新视图时遇到了一点困难。

我已将我的应用程序拆分为经典的模型、视图、视图模型结构。

目前,我正在尝试将数据从我的模型SoundScapeData.cs 传播到我的视图HomePage.xaml

我的HomePage.xaml 代码隐藏文件如下所示:

namespace AX2018
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class HomePage : ContentPage
    {
        public HomePage()
        {
            InitializeComponent();
            BindingContext = new HomePageViewModel();
        }

        private void OnConnectButtonClicked(object sender, EventArgs e)
        {
            Bluetooth bluetooth = new Bluetooth();
            bluetooth.Connect();
        }
    }
}

如您所见,我使用BindingContext 关键字绑定我的数据并将其绑定到HomePageViewModel 类的新实例。

我正在连接到蓝牙设备,因此调用bluetooth.Connect() 会在HomePage.xaml 视图中点击Button 时调用。然后,此蓝牙设备会使用某些值更新应用程序。

我想强调的是,蓝牙连接运行良好,并且已经验证可以在转换为 MVVM 设计模式之前使用 View。

我的 ViewModel HomePageViewModel.cs 如下所示:

namespace AX2018
{
    public class HomePageViewModel
    {
        private SoundScapeData _soundScapeData;

        public SoundScapeData SoundScapedata { get { return _soundScapeData; } }

        public HomePageViewModel()
        {
            _soundScapeData = new SoundScapeData();
        }
    }
}

顺便说一句,我对如何设计视图模型有点困惑,因为模型SoundScapeData.cs 目前是如此简单。这个中间视图模型是否必要?

这是我的SoundScapeData.cs 模特:

namespace AX2018
{
    public class SoundScapeData : INotifyPropertyChanged
    {
        private double _spl;

        public double SPL
        {
            get { return _spl; }
            set
            {
                _spl = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

从Model中可以看出,它实现了INotifyPropertyChanged接口,并在OnPropertyChanged()方法中有相关的事件处理函数。

我的视图,HomePage.xaml,相当长 - 它由一个 9 x 3 的网格组成,标签放置在某些位置。这些标签应该反映SoundScapeData.cs 模型(和其他模型,但这是未来)中的值。以下是相关的sn-ps:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:local="clr-namespace:AX2018"
         x:Class="AX2018.HomePage">

<ContentPage.BindingContext>
    <local:HomePageViewModel/>
</ContentPage.BindingContext>

...

<Label Text="{Binding SoundScapedata.SPL}" 
FontSize="68" Style="{StaticResource ColoredLabel}" 
Grid.Row="4" 
Grid.Column="1" 
Margin="-20"></Label>

如您所见,我已将 View HomePage.xaml 中的数据绑定到 View Model HomePageViewModel.cs,该 View Model HomePageViewModel.cs 包含 SoundScapeData.cs Model 的实例,而后者又实现了 INotifyPropertyChanged 接口。

据我了解,在使用 MVVM 设计模式时,就数据绑定而言,这是正确的方法。但是,SPL 的变化并没有反映在我的视图HomePage.xaml 中。

我在一个单独的类Bluetooth.cs 中更新了 SPL 值,该类继承自 SoundScapeData.cs 模型。这是来自Bluetooth 类的Connect() 方法的sn-p:

namespace AX2018
{
    public async void Connect()
    {
        public class Bluetooth : SoundScapeData
        ...
        var dbValue = (double)BitConverter.ToUInt16(temp1, 0) / 256 * 48;
        SPL = dbValue;
        ...
    }
}

再次,我为这个相当长的问题道歉,但我希望有人能向我指出我做错了什么。我想重申一下,在转换为 MVVM 之前,视图中的 SPL 值确实更新了,所以我的数据绑定肯定有问题。

提前谢谢你,

kdhansen

【问题讨论】:

  • 很好地组合在一起。
  • 只是说。在 MVVM 中,VM 使模型适应视图,因此应该实现 INotifyPropertyChanged 的​​类应该是 VM 而不是模型。该模型“模拟”了一个现实生活中的对象,VM 将其适应于视图。例如,您可以有一个模型 Dog,它具有腿、大小和名称,并且您的 VM 应该具有您需要显示(腿、大小...)或控制(启用的组合框、可见性标志等)的尽可能多的属性。 ) 在你看来。因此,如果您可以根据特定的大小值更改 Dog 的腿,则应该由 VM 来控制它。希望我自己解释一下。
  • @JuanCarlosRodriguez 我明白你的意思。如果我理解正确,将我的HomePage.xamlBindingContext 设置为我的模型SoundScapeData.cs 的实例将正确反映我的HomePage.xaml 视图中的更改。我刚刚这样做了,但绑定SoundScapeData.SPL 的标签仍未更新。
  • 我只是回答你,但有点长,请给我几分钟
  • @JuanCarlosRodriguez 好的,谢谢。

标签: c# xaml mvvm xamarin.forms


【解决方案1】:

我会在这里回答,因为 cmets 太短无法解释。如果我拼错了一些名字,请原谅:P 让我们总结一下您的场景:

  • 主页是您的视图
  • HomePageViewModel 是您的虚拟机
  • SoundScapeData 是您的模型

现在:

主页(查看)

我真的不知道在 Xamarin oyu 中是否必须复制这个,记住你在后面的代码中设置了它。

<ContentPage.BindingContext>
<local:HomePageViewModel/>
</ContentPage.BindingContext>

你也应该改变这个:

<Label Text="{Binding Spl}" <!--You dont need SoundScapeData anymore this is the public VM property -->
FontSize="68" Style="{StaticResource ColoredLabel}" 
Grid.Row="4" 
Grid.Column="1" 
Margin="-20"></Label>

HomePageViewModel

它应该有你需要的尽可能多的属性。假设您的模型只有一个属性 SPL 。这就是你的模型,现在你必须通过你的虚拟机把它放到视图中。所以你的虚拟机应该有一个属性(公共/私有)来适应视图和你的模型。

private string spl;
public string Spl
       {
         get {return this.spl;}
         set
            {
            if (this.spl != value)
            {
            this.spl = value;
            OnPropertyChanged("SPL);
            }

当您单击按钮并连接到蓝牙或它所做的任何事情 (;P) 时,它会更改模型,您必须更改 VM 属性。请记住,您的模型必须与您的 VM 中的实例相同。

所以...您应该附加到已更改的 Model 属性,以便更改您的 VM 属性,最好的方法是创建一个类(让我们尽可能多地应用 SOLID)

public class YourNewDataSource
{
    #region Attributes

    private readonly HomePageViewModel homePageViewModel;

    #endregion

    #region Public Methods


    public YourNewDataSource(HomePageViewModel homePageViewModel)
    {
      this.homePageViewModel = homePageViewModel;
    }

    public void Initialize()
    {
        this.homePageViewModel.SoundScapeData.PropertyChanged += this.OnHomePageModelPropertyChanged;
    }

    #endregion

    #region Event Handlers

    private void OnHomePageModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "SPL":
                this.homePageViewModel.Spl = this.homePageViewModel.SoundScapeData.Spl;
                break;
        }
    }

    #endregion
}

现在,当您启动应用程序或想要显示您的视图时,您必须使用您的 VM 创建一个新的 YourNewDataSource:

public HomePage()
    {
        InitializeComponent();
        HomePageViewModel homePageViewModel = new HomePageViewModel();
        YourNewDataSource yourNewDataSource = new YourNewDataSource(homePageViewModel)
        BindingContext = homePageViewModel;
    }

看看,试试看,如果你不明白我刚刚在这里写的一些s***:D


我刚刚查看了您的蓝牙。连接。

我不知道蓝牙类是否必须从 SoundScapeData 继承,但现在它不起作用,因为您在连接时会丢失 VM 的模型。如果您真的不需要从 SoundScapeData 继承,只需向您的连接方法添加一个参数并将 Vm.SoundScapeData 传递给它。

【讨论】:

  • 感谢您的努力,胡安。我现在正在修改您的答案,但对于您为什么在 VM 中遗漏了 INotifyPropertyChanged 接口的实现感到有些困惑。这种方法似乎没有遵循我在其他情况下使用的方法,例如在 MS Docs 的Implementing the MVVM Pattern Using the Prism Library 5.0 for WPF 中。不过,在尝试解决我的问题时,我会牢记您的回答。
  • 你需要实现它。如果您不这样做,视图将永远不会知道 VM 何时更改。如果您像正在使用的那样使用 VM,那么您刚刚创建了一个具有另一个类(模型)并将其用作数据上下文的类。您现在只使用一个名为 XXXXVM 的类直接绑定到视图的 os 建模,但它并不能真正作为 VM 工作
  • @kdhansen 看看这个:learnmvvm.com/theory.html#top
  • 感谢您的链接!看起来是学习 MVVM 的好资源。我现在让它工作,但结构并不完全符合我的意图。我将Bluetooth 类移动到HomePageViewModel 中,并将SoundScapeData 类中的所有属性移动到VM 中。我添加了一个BaseViewModel,它实现了INotifyPropertyChanged 接口,我的其他VM 将从该接口继承。所以虚拟机现在更新得很好。但这绝对是一种中间解决方法,因为 Bluetooth 类应该是一个服务,其他 VM 应该能够使用它。
  • 很高兴它有帮助:D 我的回答是基本 MVVM 应该是什么样子,你可以改进应用 SOLID 等。你的蓝牙类很好,你只需要传递正确的参数就可以了保留他们的实例。如果我的回答对您有帮助,请点赞并将其作为答案进行检查。谢谢
【解决方案2】:

您的问题是您正在更改模型的实例并失去视图的绑定。 Juan Carlos 的回应是正确的,你没有应用纯 MVVM,视图不应该知道模型,视图模型负责将模型适配到视图。 您在视图后面的代码中拥有的 OnConnectButtonClicked 也不应用 MVVM 模式,您应该使用命令。链接http://www.learnmvvm.com/ 是一个很好的起点。

如果您将代码的示例项目上传到某个存储库,我将通过应用 MVVM 和 SOLID 原则对其进行修改。

【讨论】:

  • 感谢您的回复,哈维。我要去看看学习 MVVM 并深入挖掘。
猜你喜欢
  • 1970-01-01
  • 2016-08-13
  • 1970-01-01
  • 1970-01-01
  • 2014-06-15
  • 1970-01-01
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
相关资源
最近更新 更多