我在模型中定义了一个方法,该方法将执行一个长时间运行的脚本,我希望在脚本进行时捕获输出消息,并通过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的核心原则。在没有太多可做的事情的情况下,我认为最好的方法是创建一个"服务"。
保持你的模型哑,让它只包含数据。想少。然后,利用一个实现后台工作线程的服务。让视图模型运行服务。视图模型可以调用该服务,并向该服务提供对实例化模型的引用。这样,您就不会将模型与视图模型严重耦合。