问题
I am trying to build a WPF application that uses SSH.NET to upload multiple files on a server in parallel and display a progress bar for each of them.
In the view I have the following data grid:
<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>
The ViewModel has public properties to bind DataGrid's items and SelectedItem:
public BindingList<UploadedFile> Files { get; set; }
public UploadedFile SelectedFile
{
get { return _selectedFile; }
set
{
_selectedFile = value;
NotifyOfPropertyChange(() => SelectedFile);
}
}
To get a file from hard-drive I am using MvvmDialgos library and call this method to create a new UploadedFile
and add it to Files
list:
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);
}
}
To upload the file on the server, with SSH.NET I am doing to following:
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 call's the actually ssh.net library with:
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;
}
and raises an event with the uploaded size:
private void UpdateProgresBar(ulong uploaded)
{
OnFileUploading(uploaded);
}
protected void OnFileUploading(ulong uploaded)
{
FileUploading?.Invoke(this, new UploadingEventArgs(new UploadedFile
{
SizeUploaded = (int)uploaded,
FullName = fullFileName
}));
}
In the ViewModel I am listening for the event and update the progress bar:
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);
}
}
}
My problem is when I am starting a second file transfer the progress bar for the first one stops and the one of the second transfer is going nuts, displaying random values, increasing and decreasing. My guess is somehow adding in the progress of the first file too.
Somehow I am unable to separate the progress for each progress bar.
What am I doing wrong?
回答1:
I am not sure if this is the correct fix but it seems that getting a new instance of _fileManager
from IoC (using Caliburn.Micro) every time I am calling UploadFileToServer()
solved the progressbar issue:
var manager = IoC.Get<IFileTransferManager>();
manager.OnUploading += OnUploading;
来源:https://stackoverflow.com/questions/57902034/progress-bar-for-parallel-file-upload-with-ssh-net-and-wpf-mvvm