在MVVM中更新从Model到ViewModel的进度日志消息



我在模型中定义了一个方法,该方法将执行一个长时间运行的脚本,我希望在脚本进行时捕获输出消息,并通过ViewModel输出到视图。我理解为了获得输出消息的实时更新,我应该在后台工作者中运行Model方法,并在它有输出消息要报告时引发其ReportProgress事件,以便在两个单独的线程上运行UI更新和脚本。我遇到的问题是backgroundworker对象是在ViewModel中定义的,所以使用它来调用Model方法是直接的,但是我如何从Model方法引发ReportProgress事件呢?我能想到的唯一方法是将后台工作者作为输入参数传递到方法中,但我对此感到不安。谁能告诉我,这是实现MVVM框架的正确方法吗?

这里是我的代码被剥离到最赤裸的骨头。在我的视图xaml中,我有一个TextBox绑定到Logger属性和DeployCommand命令在我的ViewModel:

            <TextBox Grid.Row="1 " Name="txtOutput"  MinHeight="40"
                 Text="{Binding Logger}"
                 IsReadOnly="True" Margin="10,10" VerticalScrollBarVisibility="Auto"
                 IsEnabled="True" MaxLines="2000" TextWrapping="WrapWithOverflow"/>
            <Button x:Name="BtnDeploy"
                    Command="{Binding DeployCommand}"
                    Content="Deploy"
                    Height="23"
                    Margin="5,2"
                    HorizontalAlignment="Right"
                    Width="125"
                    FontFamily="Kalinga"
                    AutomationProperties.AutomationId="DeployButton"/>

在我的ViewModel中,DeployCommand命令将触发OnDeploy方法,该方法将使用backgroundworker对象调用Model中的Deploy方法:

    private string logger = string.Empty;
    public string Logger
    {
        get { return logger; }
        set
        {
            logger = value;
            RaisePropertyChanged("Logger");
        }
    }
    public ICommand DeployCommand { get; private set; }
    public MainWindowViewModel()
    {
        _worker = new BackgroundWorker()
        {
            WorkerReportsProgress = true,
            WorkerSupportsCancellation = true
        };
        _worker.DoWork += worker_DoWork;
        // _worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        _worker.ProgressChanged += worker_ProgressChanged;
        DeployController = new DeploymentModel();
        this.DeployCommand = new DelegateCommand<object>(this.OnDeploy);
    }
    private void OnDeploy(object obj)
    {
        Logger += @"Offline Deployment Started" + System.Environment.NewLine;
        if (!_worker.IsBusy)
        {
            _worker.RunWorkerAsync(DeployController);
        }
    }
    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        var worker = (BackgroundWorker)sender;
        var deployModel = (DeploymentModel)e.Argument;
        deployModel.Deploy(script);
    }
    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        Logger += e.UserState.ToString();   
    }

最后在模型中:

        public bool Deploy(string ScriptFile)
    {
        bool Success = true;
        string strCmdText = string.Format(@"/c ""{0}""", ScriptFile);
        try
        {
            var startInfo = new ProcessStartInfo
                {
                    WindowStyle = ProcessWindowStyle.Hidden,
                    WorkingDirectory = kitFolder,
                    UseShellExecute = false,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    FileName = "cmd.exe",
                    CreateNoWindow = true,
                    Arguments = strCmdText,
                };
            // Launch shell command to run powersheel script
            using (Process myProcess = Process.Start(startInfo))
            {
                // capturing script output message
                myProcess.OutputDataReceived += (s, e) =>
                    {
                        LogMessage("ExecuteDeploymentKit: " + e.Data);
                    };
                myProcess.ErrorDataReceived += (s, e) =>
                    {
                        Success = false;
                        LogMessage("ExecuteDeploymentKit: ! > " + e.Data);
                    };
                myProcess.BeginErrorReadLine();
                myProcess.BeginOutputReadLine();
                System.Threading.Thread.Sleep(5000);
                myProcess.WaitForExit();
            }
        }
        catch (Exception ex)
        {
            LogMessage("ExecuteDeploymentKit: " + ex.Message);
            return false;
        }
        if (Success)
        {
            LogMessage("ExecuteDeploymentKit: Offline Deployment Kit executed successfully");
        }
        else
        {
            LogMessage("ExecuteDeploymentKit: Offline Deployment Kit failed");
        }
        return Success;
    }

我已经添加了workder_ProgressChanged来处理backgroundworker的ProgressChanged事件,以便在UI线程中更新视图,但在我的模型中没有backgroundworker对象,我无法从方法Deploy()中引发ProgressChanged事件

谢谢

标准的方法是让VM实现IProgress接口,并将VM转换作为IProgress对象传递给M。你不应该把VM传递给它,因为这可能是一个引用噩梦。

但实际上,后台worker应该在VM中实现,而不是在m中。你不应该再使用BackgroundWorker,而应该转向新的异步方法。

如果我对你的问题理解正确,你可能会通过让模型驱动你的视图模型和视图来打破MVVM的核心原则。在没有太多可做的事情的情况下,我认为最好的方法是创建一个"服务"。

保持你的模型哑,让它只包含数据。想少。然后,利用一个实现后台工作线程的服务。让视图模型运行服务。视图模型可以调用该服务,并向该服务提供对实例化模型的引用。这样,您就不会将模型与视图模型严重耦合。

最新更新