如何传递对象来处理输出事件

How to pass object to process output event

我有一个 ObservableCollection<Conversion> 队列,绑定到 ListBox 控件,ItemTemplate 包含一个 TextBlock 和一个 Button。单击该按钮时,将启动 Win32 进程。此进程有一个 ErrorDataReceived 事件处理程序方法,该方法读取进程输出并应该更新集合中 Conversion 对象的 PercentComplete 属性。 PercentComplete 绑定到 TextBlock 的文本 属性。

如何从 Win32 进程事件更新 PercentComplete?我希望将 Conversion 对象传递给 ErrorDataReceived 事件处理程序,但是 DataReceivedEventArgs 只有一个字符串类型的数据 属性。

代码如下:

XAML:

<ListBox ItemsSource="{Binding Queue}" SelectedItem="{Binding SelectedItem}">
   <ListBox.ItemTemplate>
      <DataTemplate>
         <StackPanel>
            <TextBlock Text="{Binding PercentComplete}" />
            <Button Command="convertor:Commands.RunConversion">Run</Button>
         </StackPanel>
      </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

代码隐藏:

private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
   get { return _queue; }
   set
   {
      _queue = value;
      RaisePropertyChange("Queue");
   }
}

private Conversion _selectedItem;
public Conversion SelectedItem
{
   get { return _selectedItem; }
   set
   {
      _selectedItem = value;
      RaisePropertyChange("SelectedItem");
   }
}

private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
   ...
   using (var ffmpeg = new Process())
   {
      ...
      ffmpeg.EnableRaisingEvents = true;
      ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
      // I realize it is weird I am working with ErrorDataReceived instead 
      // of OutputDataReceived event, but that's how ffmpeg.exe rolls.
      ffmpeg.Start();
      ffmpeg.BeginErrorReadLine();
   }
}

private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
   var processOutput = e.Data;
   var percentComplete = ParsePercentComplete(processOutput);

   //TODO Pass percentComplete to Conversion.PercentComplete!?
}

Class:

public class Conversion : INotifyPropertyChanged
{
   private double _percentComplete;
   public double PercentComplete
   {
      get { return _percentComplete; }
      set
      {
         _percentComplete = value;
         RaisePropertyChange("PercentComplete");
      }
   }

   public void RaisePropertyChange(string propertyName = null)
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }

   public event PropertyChangedEventHandler PropertyChanged;
}

好的,我解决了。解决方案的关键是 process.Id,它提供了对 ObservableCollection 项目特定流程的参考。

具体来说,我用 Process 进程 属性 扩展了 Conversion 以存储该特定进程的信息,然后我可以在集合中找到该项目并更新其属性来自进程事件处理程序中的进程输出。

这是更新后的代码:

代码隐藏:

private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
   get { return _queue; }
   set
   {
      _queue = value;
      RaisePropertyChange("Queue");
   }
}

private Conversion _selectedItem;
public Conversion SelectedItem
{
   get { return _selectedItem; }
   set
   {
      _selectedItem = value;
      RaisePropertyChange("SelectedItem");
   }
}

private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
   ...
   var ffmpeg = new Process();
   ffmpeg.EnableRaisingEvents = true;
   ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
   ffmpeg.Start();

   conversion.Process = ffmpeg; // This is new

   ffmpeg.BeginErrorReadLine();
}

private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
   var processOutput = e.Data;
   var percentComplete = ParsePercentComplete(processOutput);

   var processId = (sender as Process).Id; // These three lines are new
   var conversion = Queue.Where(c => c.Process.Id == processId).FirstOrDefault();
   conversion.PercentComplete = percentComplete; // WTF!!!!
}

Class

public class Conversion : INotifyPropertyChanged
{
   private double _percentComplete;
   public double PercentComplete
   {
      get { return _percentComplete; }
      set
      {
         _percentComplete = value;
         RaisePropertyChange("PercentComplete");
      }
   }
   
   // New property
   private Process _process;
   public Process Process
   {
      get { return _process; }
      set
      {
         _process= value;
         RaisePropertyChange("Process");
      }
   }
   
   public void RaisePropertyChange(string propertyName = null)
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }

   public event PropertyChangedEventHandler PropertyChanged;
}