使用 SSH.NET 和 WPF MVVM 并行文件上传的进度条
Progress bar for parallel file upload with SSH.NET and WPF MVVM
我正在尝试构建一个 WPF 应用程序,该应用程序使用 SSH.NET 在服务器上并行上传多个文件并为每个文件显示一个进度条。
在视图中我有以下数据网格:
<DataGrid
AlternatingRowBackground="LightGray"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
ItemsSource="{Binding Files, Mode=TwoWay}"
SelectedItem="{Binding SelectedFile, Mode=OneWayToSource}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DisplayName}" Header="File" />
<DataGridTextColumn Binding="{Binding Path=FileSize}" Header="Size" />
<DataGridTextColumn Binding="{Binding Path=IsUploaded, Mode=OneWay}" Header="Uploaded" />
<DataGridTextColumn Binding="{Binding Path=IsUploading, Mode=OneWay}" Header="Uploading" />
<DataGridTemplateColumn Header="Progress">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ProgressBar
Width="300"
Height="10"
Maximum="{Binding FileSize}"
Value="{Binding Path=SizeUploaded, Mode=OneWay}" />
<TextBlock
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="{Binding Path=PercentageCompleted, StringFormat={}{0}%}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Start">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="UploadFile" Content="Start" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Stop">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="StopTransfer" Content="Stop" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Remove">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="RemoveFile" Content="Remove" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel 具有 public 属性来绑定 DataGrid 的项目和 SelectedItem:
public BindingList<UploadedFile> Files { get; set; }
public UploadedFile SelectedFile
{
get { return _selectedFile; }
set
{
_selectedFile = value;
NotifyOfPropertyChange(() => SelectedFile);
}
}
要从硬盘驱动器获取文件,我正在使用 MvvmDialgos 库并调用此方法创建一个新的 UploadedFile
并将其添加到 Files
列表:
public void SelectFile()
{
var success = OpenFileDialog(this);
if (success)
{
var file = new UploadedFile
{
FullName = OpenFileDialogSettings.FileName
};
file.DisplayName = Path.GetFileName(file.FullName);
var fileInfo = new FileInfo(file.FullName);
file.FileSize = (int)fileInfo.Length;
if (Files == null)
Files = new BindingList<UploadedFile>();
Files.Add(file);
NotifyOfPropertyChange(() => Files);
}
}
上传文件到服务器,SSH.NET我正在做以下事情:
private async Task UploadFileToServer(ConnectionModel connection, string fileName, string destinationPath)
{
if (!string.IsNullOrWhiteSpace(destinationPath))
{
await Task.Run(() =>
{
var currentUploadedFile = Files.FirstOrDefault(x => x.FullName == fileName);
currentUploadedFile.IsUploading = true;
try
{
_fileManager.UploadFile(connection, fileName, destinationPath);
currentUploadedFile.IsUploaded = true;
}
catch (Exception ex)
{
BroadCastErrorMessage(ex.Message);
IsConnectionTabExpanded = true;
}
finally
{
currentUploadedFile.IsUploading = false;
}
});
}
else
{
BroadCastErrorMessage("ERROR: You must enter a destination folder!");
IsConnectionTabExpanded = true;
}
}
FileManager 调用的实际上是 ssh.net 库:
using (var fs = new FileStream(fileName, FileMode.Open))
{
fullFileName = fileName;
sftp.BufferSize = 4 * 1024;
sftp.UploadFile(fs, Path.GetFileName(fileName), true, uploadCallback: UpdateProgresBar);
OnFileUploadedSuccess($@"Successfully uploaded {fileName} in {path}.");
status = FileRenamedStatus.Uploaded;
}
并引发具有上传大小的事件:
private void UpdateProgresBar(ulong uploaded)
{
OnFileUploading(uploaded);
}
protected void OnFileUploading(ulong uploaded)
{
FileUploading?.Invoke(this, new UploadingEventArgs(new UploadedFile
{
SizeUploaded = (int)uploaded,
FullName = fullFileName
}));
}
在 ViewModel 中,我正在监听事件并更新进度条:
private void OnUploading(object sender, UploadingEventArgs e)
{
foreach (var item in Files.ToList())
{
if (item.FullName == e.UploadedFile.FullName)
{
item.SizeUploaded = e.UploadedFile.SizeUploaded;
var percentage = (double)e.UploadedFile.SizeUploaded / item.FileSize;
item.PercentageCompleted = Math.Round(percentage * 100, 2);
}
}
}
我的问题是,当我开始第二个文件传输时,第一个文件的进度条停止,第二个文件传输的进度条变得疯狂,显示随机值,增加和减少。我的猜测也是以某种方式添加了第一个文件的进度。
不知怎么的,我无法将每个进度条的进度分开。
我做错了什么?
我不确定这是否是正确的修复,但似乎每次我调用 UploadFileToServer()
时从 IoC(使用 Caliburn.Micro)获取 _fileManager
的新实例已解决进度条问题:
var manager = IoC.Get<IFileTransferManager>();
manager.OnUploading += OnUploading;
我正在尝试构建一个 WPF 应用程序,该应用程序使用 SSH.NET 在服务器上并行上传多个文件并为每个文件显示一个进度条。
在视图中我有以下数据网格:
<DataGrid
AlternatingRowBackground="LightGray"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
ItemsSource="{Binding Files, Mode=TwoWay}"
SelectedItem="{Binding SelectedFile, Mode=OneWayToSource}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DisplayName}" Header="File" />
<DataGridTextColumn Binding="{Binding Path=FileSize}" Header="Size" />
<DataGridTextColumn Binding="{Binding Path=IsUploaded, Mode=OneWay}" Header="Uploaded" />
<DataGridTextColumn Binding="{Binding Path=IsUploading, Mode=OneWay}" Header="Uploading" />
<DataGridTemplateColumn Header="Progress">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ProgressBar
Width="300"
Height="10"
Maximum="{Binding FileSize}"
Value="{Binding Path=SizeUploaded, Mode=OneWay}" />
<TextBlock
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="{Binding Path=PercentageCompleted, StringFormat={}{0}%}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Start">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="UploadFile" Content="Start" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Stop">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="StopTransfer" Content="Stop" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Remove">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button cal:Message.Attach="RemoveFile" Content="Remove" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel 具有 public 属性来绑定 DataGrid 的项目和 SelectedItem:
public BindingList<UploadedFile> Files { get; set; }
public UploadedFile SelectedFile
{
get { return _selectedFile; }
set
{
_selectedFile = value;
NotifyOfPropertyChange(() => SelectedFile);
}
}
要从硬盘驱动器获取文件,我正在使用 MvvmDialgos 库并调用此方法创建一个新的 UploadedFile
并将其添加到 Files
列表:
public void SelectFile()
{
var success = OpenFileDialog(this);
if (success)
{
var file = new UploadedFile
{
FullName = OpenFileDialogSettings.FileName
};
file.DisplayName = Path.GetFileName(file.FullName);
var fileInfo = new FileInfo(file.FullName);
file.FileSize = (int)fileInfo.Length;
if (Files == null)
Files = new BindingList<UploadedFile>();
Files.Add(file);
NotifyOfPropertyChange(() => Files);
}
}
上传文件到服务器,SSH.NET我正在做以下事情:
private async Task UploadFileToServer(ConnectionModel connection, string fileName, string destinationPath)
{
if (!string.IsNullOrWhiteSpace(destinationPath))
{
await Task.Run(() =>
{
var currentUploadedFile = Files.FirstOrDefault(x => x.FullName == fileName);
currentUploadedFile.IsUploading = true;
try
{
_fileManager.UploadFile(connection, fileName, destinationPath);
currentUploadedFile.IsUploaded = true;
}
catch (Exception ex)
{
BroadCastErrorMessage(ex.Message);
IsConnectionTabExpanded = true;
}
finally
{
currentUploadedFile.IsUploading = false;
}
});
}
else
{
BroadCastErrorMessage("ERROR: You must enter a destination folder!");
IsConnectionTabExpanded = true;
}
}
FileManager 调用的实际上是 ssh.net 库:
using (var fs = new FileStream(fileName, FileMode.Open))
{
fullFileName = fileName;
sftp.BufferSize = 4 * 1024;
sftp.UploadFile(fs, Path.GetFileName(fileName), true, uploadCallback: UpdateProgresBar);
OnFileUploadedSuccess($@"Successfully uploaded {fileName} in {path}.");
status = FileRenamedStatus.Uploaded;
}
并引发具有上传大小的事件:
private void UpdateProgresBar(ulong uploaded)
{
OnFileUploading(uploaded);
}
protected void OnFileUploading(ulong uploaded)
{
FileUploading?.Invoke(this, new UploadingEventArgs(new UploadedFile
{
SizeUploaded = (int)uploaded,
FullName = fullFileName
}));
}
在 ViewModel 中,我正在监听事件并更新进度条:
private void OnUploading(object sender, UploadingEventArgs e)
{
foreach (var item in Files.ToList())
{
if (item.FullName == e.UploadedFile.FullName)
{
item.SizeUploaded = e.UploadedFile.SizeUploaded;
var percentage = (double)e.UploadedFile.SizeUploaded / item.FileSize;
item.PercentageCompleted = Math.Round(percentage * 100, 2);
}
}
}
我的问题是,当我开始第二个文件传输时,第一个文件的进度条停止,第二个文件传输的进度条变得疯狂,显示随机值,增加和减少。我的猜测也是以某种方式添加了第一个文件的进度。
不知怎么的,我无法将每个进度条的进度分开。
我做错了什么?
我不确定这是否是正确的修复,但似乎每次我调用 UploadFileToServer()
时从 IoC(使用 Caliburn.Micro)获取 _fileManager
的新实例已解决进度条问题:
var manager = IoC.Get<IFileTransferManager>();
manager.OnUploading += OnUploading;