我在Model中定义了一个方法,该方法将执行一个长时间运行的脚本,我希望在脚本正在进行时捕获输出消息并通过ViewModel输出到View。我理解为了获得输出消息的实时更新,我应该在后台工作程序中运行Model方法,并在有输出消息报告时引发它的ReportProgress事件,以便在两个单独的线程上运行UI更新和脚本。我遇到的问题是backgroundworker对象是在ViewModel中定义的,因此使用它来调用Model方法是直截了当的,但是如何从Model方法中引发ReportProgress事件呢?我能想到的唯一方法是将backgroundworker作为输入参数传入方法,但我对此感到不安。谁能告诉我这是否是实现MVVM框架的正确方法?
这是我的代码被剥离到最裸露的骨头。在我的View xaml中,我在ViewModel中有一个TextBox绑定到Logger属性和DeployCommand命令:
<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,而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线程中的View但是在我的模型中没有backgroundworker对象,我不能从方法Deploy()中引发ProgressChanged事件
由于
答案 0 :(得分:0)
如果我理解你的问题,你可能会通过让Model驱动你的viewmodel和view来破坏MVVM的核心原则。如果没有太大的帮助,我会怀疑最好的方法是实际创建一个&#34;服务&#34;。
让你的模特愚蠢,让它只包含数据。想想POCO。然后,使用实现后台工作程序的服务。让View Model运行该服务。 View模型可以调用服务并为该服务提供对实例化模型的引用。这样,您就不会将模型与视图模型紧密耦合。
答案 1 :(得分:0)
标准方法是让您的VM实现IProgress接口,并将您的M转换为IP转发对象。你不应该把它传给VM,因为这可能是一个参考噩梦。
但实际上,后台工作者应该在VM中实现,而不是M.并且您不应该再使用BackgroundWorker并转向新的异步方法。