【问题标题】:Access method in ViewModel from another ViewModel从另一个 ViewModel 访问 ViewModel 中的方法
【发布时间】:2020-11-24 12:02:02
【问题描述】:

以前我有一个 ViewModel,它变得非常大,所以我决定将它分散到两个单独的 ViewModel 中。现在我想出了在 ViewModel 之间访问方法的问题。例如,我需要运行现在位于 DataLogModel.cs 中的 UpdateDataGridView();,并且每次更改窗口时我都需要从 ViewModel.cs 运行它(在 ViewModel.cs 中的 WinEventProc 方法内)。

正确的做法是什么?我试过了:

var DL = new DataLogModel();
DL.UpdateDataGridView();

没有错误,但也没有访问方法(= DataGrid 没有更新)。但是从 DataLogModel.cs 访问相同的方法工作得很好(= DataGrid 已按预期更新)

ViewModel.cs:

using Tracker.Models;
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;

namespace Tracker
{
    public class ChModel
    {
        private string DBconnectionString = ConfigurationManager.AppSettings["DBConnectionString"];

        /// <summary>
        /// We are interested to display only Today data in Chart
        /// /// <summary>
        public DataTable GetDataForChart()
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(DBconnectionString);
            ...
            return ndt;
        }
    }

    class ViewModel : BaseViewModel
    {
        private GetActiveWindowTitle.WinEventDelegate dele = null;

        long milliSeconds;
        TimeSpan timeSpan;
        DateTime CurrentDate;

        public static string WindowTitle;
        public static Stopwatch stopwatch = new Stopwatch();
        public static Stopwatch ManualStopwatch = new Stopwatch();

        public ViewModel()
        {
            // Let's start tracking windows
            StartWindowTracking();

        }

        /// <summary>
        /// Track windows
        /// <summary>
        private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
            int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            WindowTitle = GetActiveWindowTitle.GetActiveWindowTitleMethod();

            if (!string.IsNullOrEmpty(WindowTitle))
            {
                stopwatch.Stop();

                milliSeconds = stopwatch.ElapsedMilliseconds;
                timeSpan = stopwatch.Elapsed;
                CurrentDate = DateTime.Now;

                MainProcess.AddRecordToDatatable(WindowTitle,
                            (int)(milliSeconds / 1000), DateTime.Now.Date, MainProcess.AdminHoursCode, MainProcess.userName);

                UpdateDataGridView();

                stopwatch.Start();
            }

        }

        public void StartWindowTracking()
        {
            WindowTitle = GetActiveWindowTitle.GetActiveWindowTitleMethod();

            dele = new GetActiveWindowTitle.WinEventDelegate(WinEventProc);
            IntPtr m_hhook = GetActiveWindowTitle.SetWinEventHook(GetActiveWindowTitle.EVENT_OBJECT_FOCUS,
                GetActiveWindowTitle.EVENT_OBJECT_FOCUS, IntPtr.Zero, dele, 0, 0, GetActiveWindowTitle.WINEVENT_OUTOFCONTEXT);
        }

        public string DBconnectionString { get; internal set; }
    }
}

DataLogModel.cs:

using Tracker.Models;
using System;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace Tracker
{
    public class Model
    {
        private string DBconnectionString1 = ConfigurationManager.AppSettings["DBConnectionString"];

        /// <summary>
        /// Get data according to StartDate value
        /// <summary>
        public DataTable GetData(DateTime MyDate)
        {
            DataTable ndt = new DataTable();
            SqlConnection sqlcon = new SqlConnection(DBconnectionString1);
            ...
            return ndt;
        }
    }
    class DataLogModel : BaseViewModel
    {

        public DataLogModel()
        {
            // Update DataGrid
            UpdateDataGridView();
        }

        Model _myModel = new Model();
        private ObservableCollection<ActivityLogModel> _activityLogData = new ObservableCollection<ActivityLogModel>();
        public ObservableCollection<ActivityLogModel> ActivityLogData
        {
            get { return _activityLogData; }
            set
            {
                _activityLogData = value;
                OnPropertyChanged();
            }
        }

        public void UpdateDataGridView()
        {
            DataTable table = _myModel.GetData(StartDate);

            ActivityLogData.Clear();

            for (int i = 0; i < table.Rows.Count; ++i)
                ActivityLogData.Add(new ActivityLogModel
                {
                    WindowTitle = table.Rows[i][0].ToString(),
                    TimeSpent = (int)table.Rows[i][1],
                    DateToday = Convert.ToDateTime(table.Rows[i][2]),
                    Project = table.Rows[i][3].ToString(),
                    UserName = table.Rows[i][4].ToString(),
                });
        }
    }
}

MainViewModel.cs:

using Tracker.Models;
using System.Windows;
using System.Windows.Input;

namespace Tracker
{
    class MainViewModel
    {
        public WindowViewModel WindowViewModel { get; set; }
        public ViewModel ViewModel { get; set; }
        public SettingsViewModel SettingsViewModel { get; set; }
        public DataLogModel DataLogModel { get; set; }
    }
}

【问题讨论】:

  • 管理多个视图模型的常用方法是拥有一个具有公共属性的主视图模型,该属性包含对某些子视图模型的引用。
  • @Clemens 我已经添加了 MainViewModel.cs。你是这个意思吗?但是也不可能 ´DataLogModel.UpdateDataGridView();`
  • 不知道您在 MainViewModel 中对这些属性究竟做了什么,但当然所有这些属性都应该被初始化,即获得分配的值。然后将 MainViewModel 的一个实例传递给视图的 DataContext。在视图中绑定到子视图模型属性,例如{Binding DataLogModel.ActivityLogData}"。

标签: c# wpf mvvm viewmodel


【解决方案1】:

您必须始终注意在正确的实例上进行操作:

var instanceA = new MyClass() { Value = 5 };
var instanceB = new MyClass() { Value = 5 };

// instanceA and instanceB are two different instances 
// i.e. referencing two different memory addresses
ReferenceEquals(instanceA, instanceB); // false

// Does not modify the Value of instanceB
instanceA.Value = 10; 

// instanceA.Value is 10 while instanceB.Value is still 5
instanceA.Value == instanceB.Value // false

当您将MainViewModel 的实例分配给视图的DataContext 时,您必须使用此实例并且仅使用此实例(及其聚合实例)来修改视图。

目前您正在使用DataLogModel 的两个实例,一个被视图引用,一个被断开连接:

// This instance is disconnected from the view. 
// It is not the same instance that is referenced inside MainViewModel
var DL = new DataLogModel();
DL.UpdateDataGridView();

您必须确保使用正确的引用正确初始化所有属性。如果ViewModel 应该访问DataLogModel,那么ViewModel 需要聚合一个(共享)对这种类型实例的引用:

ViewModel.cs

class ViewModel : BaseViewModel
{
  private DataLogModel DataLogModel { get; }

  public ViewModel(DataLogModel dataLogModel)
  {
    // Aggregate an instance of DataLogModel.
    // This enables the instantiating class to inject a *shared* instance.
    this.DataLogModel = dataLogModel;
  }

  private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
            int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
  {
    ...
    
    this.DataLogModel.UpdateDataGridView();

    ...
  }
}

MainViewModel.cs
然后使用MainViewModel 使用的相同(共享)DataLogModel 实例正确初始化ViewModel:

class MainViewModel
{
  private ViewModel ViewModel { get; }
  private DataLogModel DataLogModel { get; }

  public MainViewModel()
  {
    var sharedDataLogModelInstance = new DataLogModel();
    this.DataLogModel = sharedDataLogModelInstance;

    // Allow ViewModel to reference the same instance of DataLogModel
    this.ViewModel = new ViewModel(sharedDataLogModelInstance);
  }
}

【讨论】:

    【解决方案2】:
    var DL = new DataLogModel();
    DL.UpdateDataGridView();
    

    此代码的问题在于 DL 是一个“新的”DataLogModel...它与您的 DataGrid 所在的 DataLogModel 实例不同,因此它无法以您想要的方式刷新它。

    如果您尝试在“正确的”DataLogModel 上调用该方法,您需要的是对该 DataLogModel 的引用。例如,当您创建 ViewModel 和 DataLogModel 时,您会将 DataLogModel 的特定实例传递给 ViewModel(通常在构造函数本身中)以便能够调用它。

    【讨论】:

    • 感谢您的回答!在我的情况下,有没有可能提供一些热的代码示例来运行这个特定的方法?或者任何示例的链接?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多