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