Refactor conversion job system.
This commit is contained in:
parent
890ed87cf8
commit
c9d2c77340
@ -197,7 +197,9 @@ namespace FileConverter
|
||||
for (int index = 0; index < filePaths.Count; index++)
|
||||
{
|
||||
string inputFilePath = filePaths[index];
|
||||
ConversionJob conversionJob = new ConversionJob(conversionPreset, inputFilePath);
|
||||
ConversionJob conversionJob = ConversionJobFactory.Create(conversionPreset);
|
||||
conversionJob.PrepareConversion(inputFilePath);
|
||||
|
||||
this.conversionJobs.Add(conversionJob);
|
||||
}
|
||||
|
||||
|
@ -4,46 +4,29 @@ namespace FileConverter.ConversionJobs
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class ConversionJob : INotifyPropertyChanged
|
||||
{
|
||||
private readonly Regex durationRegex = new Regex(@"Duration:\s*([0-9][0-9]):([0-9][0-9]):([0-9][0-9])\.([0-9][0-9]),.*bitrate:\s*([0-9]+) kb\/s");
|
||||
private readonly Regex progressRegex = new Regex(@"size=\s*([0-9]+)kB\s+time=([0-9][0-9]):([0-9][0-9]):([0-9][0-9]).([0-9][0-9])\s+bitrate=\s*([0-9]+.[0-9])kbits\/s");
|
||||
|
||||
private TimeSpan fileDuration;
|
||||
private TimeSpan actualConvertedDuration;
|
||||
|
||||
private ProcessStartInfo ffmpegProcessStartInfo;
|
||||
|
||||
private float progress;
|
||||
|
||||
private ConversionState state;
|
||||
|
||||
private string exitingMessage;
|
||||
private float progress = 0f;
|
||||
private ConversionState state = ConversionState.Unknown;
|
||||
private string errorMessage = string.Empty;
|
||||
|
||||
public ConversionJob()
|
||||
{
|
||||
this.ConversionPreset = null;
|
||||
this.progress = 0f;
|
||||
this.InputFilePath = string.Empty;
|
||||
this.OutputFilePath = string.Empty;
|
||||
this.State = ConversionState.Unknown;
|
||||
this.ExitingMessage = string.Empty;
|
||||
this.ConversionPreset = null;
|
||||
this.InputFilePath = string.Empty;
|
||||
}
|
||||
|
||||
public ConversionJob(ConversionPreset conversionPreset, string inputFilePath)
|
||||
public ConversionJob(ConversionPreset conversionPreset) : this()
|
||||
{
|
||||
if (conversionPreset == null)
|
||||
{
|
||||
throw new ArgumentNullException("conversionPreset");
|
||||
}
|
||||
|
||||
this.ConversionPreset = conversionPreset;
|
||||
this.InputFilePath = inputFilePath;
|
||||
|
||||
string extension = System.IO.Path.GetExtension(inputFilePath);
|
||||
this.OutputFilePath = inputFilePath.Substring(0, inputFilePath.Length - extension.Length) + "." + this.ConversionPreset.OutputType.ToString().ToLowerInvariant();
|
||||
|
||||
this.Initialize();
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
@ -97,27 +80,47 @@ namespace FileConverter.ConversionJobs
|
||||
return this.progress;
|
||||
}
|
||||
|
||||
private set
|
||||
protected set
|
||||
{
|
||||
this.progress = value;
|
||||
this.NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string ExitingMessage
|
||||
public string ErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.exitingMessage;
|
||||
return this.errorMessage;
|
||||
}
|
||||
|
||||
set
|
||||
private set
|
||||
{
|
||||
this.exitingMessage = value;
|
||||
this.errorMessage = value;
|
||||
this.NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareConversion(string inputFilePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputFilePath))
|
||||
{
|
||||
throw new ArgumentNullException("inputFilePath");
|
||||
}
|
||||
|
||||
if (this.ConversionPreset == null)
|
||||
{
|
||||
throw new Exception("The conversion preset must be valid.");
|
||||
}
|
||||
|
||||
this.InputFilePath = inputFilePath;
|
||||
this.OutputFilePath = this.GenerateOutputPath(this.InputFilePath);
|
||||
|
||||
this.Initialize();
|
||||
|
||||
this.State = ConversionState.Ready;
|
||||
}
|
||||
|
||||
public void StartConvertion()
|
||||
{
|
||||
if (this.ConversionPreset == null)
|
||||
@ -125,140 +128,51 @@ namespace FileConverter.ConversionJobs
|
||||
throw new Exception("The conversion preset must be valid.");
|
||||
}
|
||||
|
||||
if (this.State != ConversionState.Ready)
|
||||
{
|
||||
throw new Exception("Invalid conversion state.");
|
||||
}
|
||||
|
||||
Diagnostics.Log("Convert file {0} to {1}.", this.InputFilePath, this.OutputFilePath);
|
||||
|
||||
string arguments = string.Empty;
|
||||
switch (this.ConversionPreset.OutputType)
|
||||
{
|
||||
case OutputType.Mp3:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" -qscale:a 2 \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
case OutputType.Ogg:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" -acodec libvorbis -qscale:a 2 \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
case OutputType.Flac:
|
||||
case OutputType.Wav:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Converter not implemented for output file type " + this.ConversionPreset.OutputType);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(arguments) || this.ffmpegProcessStartInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.State = ConversionState.InProgress;
|
||||
|
||||
try
|
||||
this.Convert();
|
||||
|
||||
if (this.State != ConversionState.Failed)
|
||||
{
|
||||
Diagnostics.Log(string.Empty);
|
||||
this.ffmpegProcessStartInfo.Arguments = arguments;
|
||||
Diagnostics.Log("Execute command: {0} {1}.", this.ffmpegProcessStartInfo.FileName, this.ffmpegProcessStartInfo.Arguments);
|
||||
using (Process exeProcess = Process.Start(this.ffmpegProcessStartInfo))
|
||||
{
|
||||
using (StreamReader reader = exeProcess.StandardError)
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
string result = reader.ReadLine();
|
||||
|
||||
this.ParseFFMPEGOutput(result);
|
||||
|
||||
Diagnostics.Log("ffmpeg output: {0}", result);
|
||||
}
|
||||
}
|
||||
|
||||
exeProcess.WaitForExit();
|
||||
}
|
||||
// Convertion succeed !
|
||||
this.Progress = 1f;
|
||||
this.State = ConversionState.Done;
|
||||
Diagnostics.Log("\nDone!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.State = ConversionState.Failed;
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(this.ExitingMessage))
|
||||
{
|
||||
this.State = ConversionState.Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
this.Progress = 1f;
|
||||
this.State = ConversionState.Done;
|
||||
|
||||
Diagnostics.Log("\nDone!");
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
protected virtual void Convert()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
protected void ConvertionFailed(string exitingMessage)
|
||||
{
|
||||
this.State = ConversionState.Failed;
|
||||
}
|
||||
|
||||
protected virtual string GenerateOutputPath(string inputFilePath)
|
||||
{
|
||||
if (this.ConversionPreset == null)
|
||||
{
|
||||
throw new Exception("The conversion preset must be valid.");
|
||||
}
|
||||
|
||||
this.ffmpegProcessStartInfo = null;
|
||||
|
||||
string applicationDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
string ffmpegPath = string.Format("{0}\\ffmpeg.exe", applicationDirectory);
|
||||
if (!System.IO.File.Exists(ffmpegPath))
|
||||
{
|
||||
Diagnostics.Log("Can't find ffmpeg executable ({0}). Try to reinstall the application.", ffmpegPath);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ffmpegProcessStartInfo = new ProcessStartInfo(ffmpegPath);
|
||||
|
||||
this.ffmpegProcessStartInfo.CreateNoWindow = true;
|
||||
this.ffmpegProcessStartInfo.UseShellExecute = false;
|
||||
this.ffmpegProcessStartInfo.RedirectStandardOutput = true;
|
||||
this.ffmpegProcessStartInfo.RedirectStandardError = true;
|
||||
|
||||
this.State = ConversionState.Ready;
|
||||
string extension = System.IO.Path.GetExtension(inputFilePath);
|
||||
return inputFilePath.Substring(0, inputFilePath.Length - extension.Length) + "." + this.ConversionPreset.OutputType.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void ParseFFMPEGOutput(string input)
|
||||
{
|
||||
Match match = this.durationRegex.Match(input);
|
||||
if (match.Success && match.Groups.Count >= 6)
|
||||
{
|
||||
int hours = int.Parse(match.Groups[1].Value);
|
||||
int minutes = int.Parse(match.Groups[2].Value);
|
||||
int seconds = int.Parse(match.Groups[3].Value);
|
||||
int milliseconds = int.Parse(match.Groups[4].Value);
|
||||
float bitrate = float.Parse(match.Groups[5].Value);
|
||||
this.fileDuration = new TimeSpan(0, hours, minutes, seconds, milliseconds);
|
||||
return;
|
||||
}
|
||||
|
||||
match = this.progressRegex.Match(input);
|
||||
if (match.Success && match.Groups.Count >= 7)
|
||||
{
|
||||
int size = int.Parse(match.Groups[1].Value);
|
||||
int hours = int.Parse(match.Groups[2].Value);
|
||||
int minutes = int.Parse(match.Groups[3].Value);
|
||||
int seconds = int.Parse(match.Groups[4].Value);
|
||||
int milliseconds = int.Parse(match.Groups[5].Value);
|
||||
float bitrate = 0f;
|
||||
float.TryParse(match.Groups[6].Value, out bitrate);
|
||||
|
||||
this.actualConvertedDuration = new TimeSpan(0, hours, minutes, seconds, milliseconds);
|
||||
|
||||
this.Progress = this.actualConvertedDuration.Ticks / (float)this.fileDuration.Ticks;
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.Contains("Exiting."))
|
||||
{
|
||||
this.ExitingMessage = input;
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (this.PropertyChanged != null)
|
||||
{
|
||||
|
@ -0,0 +1,14 @@
|
||||
// <copyright file="ConversionJobFactory.cs" company="AAllard">License: http://www.gnu.org/licenses/gpl.html GPL version 3.</copyright>
|
||||
|
||||
namespace FileConverter.ConversionJobs
|
||||
{
|
||||
public static class ConversionJobFactory
|
||||
{
|
||||
public static ConversionJob Create(ConversionPreset conversionPreset)
|
||||
{
|
||||
ConversionJob conversionJob = new ConversionJob_FFMPEG(conversionPreset);
|
||||
|
||||
return conversionJob;
|
||||
}
|
||||
}
|
||||
}
|
154
Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs
Normal file
154
Application/FileConverter/ConversionJobs/ConversionJob_FFMPEG.cs
Normal file
@ -0,0 +1,154 @@
|
||||
// <copyright file="ConversionJob_FFMPEG.cs" company="AAllard">License: http://www.gnu.org/licenses/gpl.html GPL version 3.</copyright>
|
||||
|
||||
namespace FileConverter.ConversionJobs
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class ConversionJob_FFMPEG : ConversionJob
|
||||
{
|
||||
private readonly Regex durationRegex = new Regex(@"Duration:\s*([0-9][0-9]):([0-9][0-9]):([0-9][0-9])\.([0-9][0-9]),.*bitrate:\s*([0-9]+) kb\/s");
|
||||
private readonly Regex progressRegex = new Regex(@"size=\s*([0-9]+)kB\s+time=([0-9][0-9]):([0-9][0-9]):([0-9][0-9]).([0-9][0-9])\s+bitrate=\s*([0-9]+.[0-9])kbits\/s");
|
||||
|
||||
private TimeSpan fileDuration;
|
||||
private TimeSpan actualConvertedDuration;
|
||||
|
||||
private ProcessStartInfo ffmpegProcessStartInfo;
|
||||
|
||||
public ConversionJob_FFMPEG() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public ConversionJob_FFMPEG(ConversionPreset conversionPreset) : base(conversionPreset)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
if (this.ConversionPreset == null)
|
||||
{
|
||||
throw new Exception("The conversion preset must be valid.");
|
||||
}
|
||||
|
||||
this.ffmpegProcessStartInfo = null;
|
||||
|
||||
string applicationDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
string ffmpegPath = string.Format("{0}\\ffmpeg.exe", applicationDirectory);
|
||||
if (!System.IO.File.Exists(ffmpegPath))
|
||||
{
|
||||
Diagnostics.Log("Can't find ffmpeg executable ({0}). Try to reinstall the application.", ffmpegPath);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ffmpegProcessStartInfo = new ProcessStartInfo(ffmpegPath);
|
||||
|
||||
this.ffmpegProcessStartInfo.CreateNoWindow = true;
|
||||
this.ffmpegProcessStartInfo.UseShellExecute = false;
|
||||
this.ffmpegProcessStartInfo.RedirectStandardOutput = true;
|
||||
this.ffmpegProcessStartInfo.RedirectStandardError = true;
|
||||
|
||||
string arguments = string.Empty;
|
||||
switch (this.ConversionPreset.OutputType)
|
||||
{
|
||||
case OutputType.Mp3:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" -qscale:a 2 \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
case OutputType.Ogg:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" -acodec libvorbis -qscale:a 2 \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
case OutputType.Flac:
|
||||
case OutputType.Wav:
|
||||
arguments = string.Format("-n -stats -i \"{0}\" \"{1}\"", this.InputFilePath, this.OutputFilePath);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Converter not implemented for output file type " + this.ConversionPreset.OutputType);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(arguments))
|
||||
{
|
||||
throw new Exception("Invalid ffmpeg process arguments.");
|
||||
}
|
||||
|
||||
this.ffmpegProcessStartInfo.Arguments = arguments;
|
||||
}
|
||||
|
||||
protected override void Convert()
|
||||
{
|
||||
if (this.ConversionPreset == null)
|
||||
{
|
||||
throw new Exception("The conversion preset must be valid.");
|
||||
}
|
||||
|
||||
Diagnostics.Log("Convert file {0} to {1}.", this.InputFilePath, this.OutputFilePath);
|
||||
Diagnostics.Log(string.Empty);
|
||||
Diagnostics.Log("Execute command: {0} {1}.", this.ffmpegProcessStartInfo.FileName, this.ffmpegProcessStartInfo.Arguments);
|
||||
|
||||
try
|
||||
{
|
||||
using (Process exeProcess = Process.Start(this.ffmpegProcessStartInfo))
|
||||
{
|
||||
using (StreamReader reader = exeProcess.StandardError)
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
string result = reader.ReadLine();
|
||||
|
||||
this.ParseFFMPEGOutput(result);
|
||||
|
||||
Diagnostics.Log("ffmpeg output: {0}", result);
|
||||
}
|
||||
}
|
||||
|
||||
exeProcess.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.ConvertionFailed("Failed to launch FFMPEG process.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseFFMPEGOutput(string input)
|
||||
{
|
||||
Match match = this.durationRegex.Match(input);
|
||||
if (match.Success && match.Groups.Count >= 6)
|
||||
{
|
||||
int hours = int.Parse(match.Groups[1].Value);
|
||||
int minutes = int.Parse(match.Groups[2].Value);
|
||||
int seconds = int.Parse(match.Groups[3].Value);
|
||||
int milliseconds = int.Parse(match.Groups[4].Value);
|
||||
float bitrate = float.Parse(match.Groups[5].Value);
|
||||
this.fileDuration = new TimeSpan(0, hours, minutes, seconds, milliseconds);
|
||||
return;
|
||||
}
|
||||
|
||||
match = this.progressRegex.Match(input);
|
||||
if (match.Success && match.Groups.Count >= 7)
|
||||
{
|
||||
int size = int.Parse(match.Groups[1].Value);
|
||||
int hours = int.Parse(match.Groups[2].Value);
|
||||
int minutes = int.Parse(match.Groups[3].Value);
|
||||
int seconds = int.Parse(match.Groups[4].Value);
|
||||
int milliseconds = int.Parse(match.Groups[5].Value);
|
||||
float bitrate = 0f;
|
||||
float.TryParse(match.Groups[6].Value, out bitrate);
|
||||
|
||||
this.actualConvertedDuration = new TimeSpan(0, hours, minutes, seconds, milliseconds);
|
||||
|
||||
this.Progress = this.actualConvertedDuration.Ticks / (float)this.fileDuration.Ticks;
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.Contains("Exiting."))
|
||||
{
|
||||
this.ConvertionFailed(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -76,7 +76,8 @@
|
||||
<Compile Include="Controls\EncodingQualitySliderControl.xaml.cs">
|
||||
<DependentUpon>EncodingQualitySliderControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ConversionJobs\ConversionJob_MP3.cs" />
|
||||
<Compile Include="ConversionJobs\ConversionJobFactory.cs" />
|
||||
<Compile Include="ConversionJobs\ConversionJob_FFMPEG.cs" />
|
||||
<Compile Include="EncodingMode.cs" />
|
||||
<Compile Include="ValueConverters\ApplicationVersionToApplicationName.cs" />
|
||||
<Compile Include="ConversionPreset.cs" />
|
||||
|
@ -12,5 +12,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=MethodPropertyEvent/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -4,7 +4,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FileConverter"
|
||||
xmlns:valueConverters="clr-namespace:FileConverter.ValueConverters"
|
||||
xmlns:ConversionJobs="clr-namespace:FileConverter.ConversionJobs" mc:Ignorable="d" x:Class="FileConverter.MainWindow"
|
||||
xmlns:conversionJobs="clr-namespace:FileConverter.ConversionJobs" mc:Ignorable="d" x:Class="FileConverter.MainWindow"
|
||||
x:Name="FileConverterMainWindow" Height="500" Width="950" MinHeight="250" MinWidth="450" WindowStartupLocation="CenterScreen" Icon="/FileConverter;component/Resources/ApplicationIcon-16x16.ico">
|
||||
<Window.Resources>
|
||||
<valueConverters:ApplicationVersionToApplicationName x:Key="ApplicationVersionToApplicationName"/>
|
||||
@ -54,7 +54,7 @@
|
||||
<TextBlock Text="Converted from " FontSize="11" FontStyle="Italic" />
|
||||
<TextBlock Text="{Binding InputFilePath}" FontSize="11" FontStyle="Italic" />
|
||||
</WrapPanel>
|
||||
<TextBlock Text="{Binding ExitingMessage}" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
|
||||
<TextBlock Text="{Binding ErrorMessage}" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding State}" FontWeight="Bold" Foreground="{Binding State, ConverterParameter=Foreground, Converter={StaticResource ConversionStateToColor}}" />
|
||||
@ -65,7 +65,7 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.DataContext>
|
||||
<ConversionJobs:ConversionJob/>
|
||||
<conversionJobs:ConversionJob/>
|
||||
</ItemsControl.DataContext>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
Loading…
x
Reference in New Issue
Block a user