Search code examples
wpfeventsdata-bindingevent-handlingwin32-process

How to pass object to process output event


I have an ObservableCollection<Conversion> Queue, bound to ListBox control with ItemTemplate containing a TextBlock and a Button. When the button is clicked, a Win32 process starts. This process has an ErrorDataReceived event handler method which reads the process output and is supposed to update the PercentComplete property of the Conversion object in the collection. PercentComplete is bound to TextBlock's Text property.

How do I update PercentComplete from Win32 process event? I was hoping to pass the Conversion object to the ErrorDataReceived event handler, but the DataReceivedEventArgs only has a single Data property of type string.

Here is the code:

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>

Code-behind:

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;
}

Solution

  • Ok, I solved it. The key to the solution was the process.Id which provides the reference to the process specific to the ObservableCollection item.

    Specifically, I expanded the Conversion with Process Process property to store the information of that particular process, and then I can find the item in the collection and update its properties from process output in process' event handler.

    Here is the updated code:

    Code-behind:

    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;
    }