【问题标题】:How to notify View from ViewModel without breaking MVVM?如何在不破坏 MVVM 的情况下从 ViewModel 通知 View?
【发布时间】:2019-10-16 15:08:10
【问题描述】:

我最近开始在学校尝试 MVVM 模式,想知道最好的方法(如果有的话)是从 ViewModel 通知 View,让视图知道在不破坏 MVVM 的情况下运行方法?基本上是让视图知道某事是否成功,例如登录尝试或尝试连接到数据库?

一个例子可以是一个登录页面,只有在登录成功时主窗口才应该将内容更改为新页面,如果没有,则应该显示一个消息框

编辑:

我正在使用 .NET

到目前为止我所尝试的:

查看:

<Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:View.Pages" xmlns:ViewModels="clr-namespace:ViewModel.ViewModels;assembly=ViewModel" x:Class="View.Pages.Start_Page"
      mc:Ignorable="d" 
      d:DesignHeight="720" d:DesignWidth="1280"
      Title="Start_Page">

    <Page.DataContext>
        <ViewModels:Start_Page_ViewModel/>
    </Page.DataContext>

背后的代码:

public Start_Page()
        {
            InitializeComponent();

            Start_Page_ViewModel currentDataContext = DataContext as Start_Page_ViewModel;

            currentDataContext.CurrentUserIDGotten += GoToMenu;
        }

        private void GoToMenu(int result)
        {
            if (result == -1)
            {
                MessageBox.Show("User credentials incorrect");
            }
            else if (result == -2)
            {
                MessageBox.Show("Connection failed");
            }
            else
            {
                Application.Current.MainWindow.Content = new Menu_Page();
            }
        }

视图模型:

public class Start_Page_ViewModel
    {

        private string userName;
        private string userPassword;

        public string UserName { get => userName; set => userName = value; }

        public string UserPassword { get => userPassword; set => userPassword = value; }

        private RelayCommand logIn;

        public RelayCommand LogIn => logIn;


        public delegate void CurrentUserIDGottenEventHandler(int result);
        public event CurrentUserIDGottenEventHandler CurrentUserIDGotten;

        public Start_Page_ViewModel()
        {
            logIn = new RelayCommand(LogInToProgram, CanLogIn);
        }

        public void LogInToProgram(object o)
        {
            PasswordBox passwordBox = o as PasswordBox;

            ViewModelController.Instance.CurrentUserID = Database_Controller.Instance.SignIn(userName, passwordBox.Password);

            OnUserIDGotten(ViewModelController.Instance.CurrentUserID);
        }

        public bool CanLogIn(object o)
        {
            if (userName != null)
            {
                return true;
            }
            return false;
        }

        protected virtual void OnUserIDGotten(int result)
        {
            if (CurrentUserIDGotten != null)
            {
                CurrentUserIDGotten(result);
            }
        }
    }

【问题讨论】:

  • 你能告诉我们你到目前为止尝试了什么吗?您要问的是 MVVM 模式的功能。你用的是什么框架?

标签: c# mvvm


【解决方案1】:

通常,ViewModel 通过数据绑定与 View 进行通信。 ViewModel 可能会公开 View 将绑定到的属性,例如 LoginSuccessful。然后,当属性更新时,视图将收到一个 PropertyChanged 通知并更改其外观的某些方面。如何做到这一点各不相同;例如,XAML 中的文本属性可以直接绑定到底层 ViewModel 属性:

<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}"/>

ViewModel 可能如下所示:

public class UserViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    public string Username {
        get { return _username; }
        set {
            _username = value;
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Username"));
        }
    }
    private string _username;

    public UserViewModel() { }
}

每当 UserViewModel 类上的 Username 属性发生变化时,文本框都会更新以显示新值。

但是,这种方法并不适用于所有情况。使用布尔值时,实现数据触发器通常很有用:

<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}">
    <TextBlock.Style>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Source={StaticResource UserViewModel}, Path=IsTaken}" Value="True">
                            <Setter Property="Background" Value="Red"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBox>

如果在 ViewModel 上将 IsTaken 属性设置为 true,则此代码扩展了前面的示例,将文本框的背景设置为红色。数据触发器的一个好处是它们会自行重置。例如,如果该值设置为 false,则背景将恢复为原始颜色。

如果您想反其道而行之,并通知 ViewModel 用户输入或类似的重要事件,您可以使用命令。命令可以绑定到 XAML 中的属性,并由 ViewModel 实现。当用户执行特定操作时调用它们,例如单击按钮。可以通过实现 ICommand 接口来创建命令。

【讨论】:

    【解决方案2】:

    纯粹的方式,没有指定的框架。

    1. 创建事件委托(或侦听器接口),与视图模型关联
    2. 在视图上注册事件处理程序
    3. 视图模型更改时触发事件

    喜欢这个

    using System;
    
    public class MainClass {
      public static void Main (string[] args) {
        ViewModel m = new ViewModel();
        View v = new View();
        v.Model = m;
        m.MakeSomeChange();
      }
    }
    
    public class View {
      private IViewModel _model;
      public IViewModel Model {
        get {
          return _model;
        }
        set {
          if(_model != null) {
            _model.OnChanged -= OnChanged;
          }
          if(value != null) {
            value.OnChanged += OnChanged;
          }
          _model = value;
        }
      }
      private void OnChanged(){
        //update view
        Console.WriteLine ("View Updated");
      }
    }
    
    public delegate void ViewChangeDelegate();
    
    public interface IViewModel {
      event ViewChangeDelegate OnChanged;
    }
    
    public class ViewModel: IViewModel {
      public event ViewChangeDelegate OnChanged;
    
      public void MakeSomeChange() {
        //make some change in the view Model
        OnChanged.Invoke();
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-12-04
      • 2014-08-22
      • 2019-06-27
      • 1970-01-01
      • 1970-01-01
      • 2010-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多