从另一个 ViewModel 访问 ViewModel 中的方法

Access method in ViewModel from another ViewModel

以前我有一个 ViewModel,它变得相当大,所以我决定将它分散到两个单独的 ViewModel 中。现在我想出了在 ViewModel 之间访问方法的问题。我需要 运行 例如现在位于 DataLogModel.csUpdateDataGridView(); 并且每次 window 时我都需要从 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(),
                });
        }
    }
}

主要ViewModel.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; }
    }
}
var DL = new DataLogModel();
DL.UpdateDataGridView();

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

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

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

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();

    ...
  }
}

主要ViewModel.cs
然后使用 MainViewModel:

使用的相同 (shared) 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);
  }
}