Reputation: 355
I have been trying for some hours to get my ListView with ObservableCollection to work. However, no luck. I also read through some posts in here and there and try to match and still no good. Please give me pointer on where it went wrong.
Basically, what I am trying to do is split logic from VM to Helper class. And the logic in this class will update the data but VM does not aware of it.
My problem is in copying file function, the job status does not change the view data. I tried Messenger.Default.Send (from the helper) and Messenger register (in VM), to accept the changes, still no luck.
I am using MVVM Light, WPF, C# by the way.
Here is my code for Model.
public class MyFile : ViewModelBase
{
public string fullFileName { get; set; }
public string fileName { get; set; }
private string _jobStatus;
public string jobStatus
{
get { return _jobStatus; }
set { Set(ref _jobStatus, value); }
}
}
Here is my code for Helper.
class FileHelper
{
public List<MyFile> GetFileName(string dir)
{
List<MyFile> lstFiles = new List<MyFile>();
foreach (var file in (new DirectoryInfo(dir).GetFiles()))
{
if (file.Name.ToLower().Contains("xls") && !file.Name.Contains("~$"))
lstFiles.Add(new MyFile() { fullFileName = file.FullName, fileName = file.Name, jobStatus = "-" });
}
return lstFiles;
}
public bool CopyFiles(string destDir, List<MyFile> lstFiles)
{
try
{
int counter = 0;
foreach (MyFile f in lstFiles)
{
f.jobStatus = "Copying";
File.Copy(f.fullFileName, Path.Combine(destDir, f.fileName),true);
f.jobStatus = "Finished";
counter += 1;
Console.WriteLine("M: " + DateTime.Now.ToString("hh:mm:ss") + " " + counter);
Messenger.Default.Send(counter, "MODEL");
}
return true;
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
return false;
}
}
}
Here is my code for VM.
public class MainViewModel : ViewModelBase
{
public ICommand CmdJob { get; private set; }
private ObservableCollection<MyFile> fileList;
public ObservableCollection<MyFile> FileList
{
get { return fileList; }
set { Set(ref fileList, value); }
}
private string counter;
public string Counter
{
get { return counter; }
set { Set(ref counter, value); }
}
public MainViewModel()
{
Messenger.Default.Register<int>(this, "MODEL", UpdateCounter);
CmdJob = new RelayCommand<object>(Action_Job);
Counter = "0";
}
private void UpdateCounter(int bgCounter)
{
Counter = bgCounter.ToString();
RaisePropertyChanged("FileList");
Console.WriteLine("VM: " + DateTime.Now.ToString("hh:mm:ss") + " " + Counter);
}
private void Action_Job(object tag)
{
if (tag == null || string.IsNullOrEmpty(tag.ToString()))
return;
switch (tag.ToString())
{
case "GET": GetFile(); break;
case "COPY": CopyFile(); break;
}
}
private void GetFile()
{
Counter = "0";
List<MyFile> myFs = new FileHelper().GetFileName(@"C:\Test\Original\");
FileList = new ObservableCollection<MyFile>(myFs);
}
private void CopyFile()
{
if (new FileHelper().CopyFiles(@"C:\Test\Destination\", fileList.ToList()))
Messenger.Default.Send("Files copying finished", "VM");
else
Messenger.Default.Send("Files copying failed", "VM");
}
}
And here is my XAML.
<ListView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding FileList}" Margin="5,5,0,5" HorizontalAlignment="Left" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.View>
<GridView>
<GridViewColumn Header="File Name" Width="170" DisplayMemberBinding="{Binding fileName}" />
<GridViewColumn Header="Status" Width="170" DisplayMemberBinding="{Binding jobStatus}" >
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Vertical">
<Button Content="Get file list" Tag="GET" Command="{Binding CmdJob}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Width="80" Height="25" Margin="0,50,0,50"/>
<Button Content="Copy file" Tag="COPY" Command="{Binding CmdJob}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Width="80" Height="25" />
<Label Content="{Binding Counter}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Height="25" FontWeight="Bold" Foreground="Red" Margin="0,50,0,0"/>
</StackPanel>
I read through some posts, it said for "change", ObservableCollection will not reflect the changes to View. So, I follow those posts solution (to use Notify change in Model class), not working for me.
For me, my individual file is a big file size, so I can see there is no update on my VM.
If you test with small file size, you will not see the difference.
I tried to use Messenger method, it also not updating on View but my VM can accept incoming message with no issue.
Upvotes: 1
Views: 395
Reputation: 355
Thanks to Clemens and mm8, I managed to change it to async and await for UI updating.
I assume my way of implementing is still reasonable (compare to mm8).
private async void CopyFile()
{
var fh = new FileHelper();
bool result = await fh.CopyFilesAsync(@"C:\Test\Destination\", fileList.ToList());
if (result)
Messenger.Default.Send("Files copying finished", "VM");
else
Messenger.Default.Send("Files copying failed", "VM");
}
public async Task<bool> CopyFilesAsync(string destDir, List<MyFile> lstFiles)
{
try
{
int counter = 0;
await Task.Run(() =>
{
foreach (MyFile f in lstFiles)
{
f.jobStatus = "Copying";
Thread.Sleep(500);
File.Copy(f.fullFileName, Path.Combine(destDir, f.fileName), true);
f.jobStatus = "Finished";
counter += 1;
Messenger.Default.Send(counter, "MODEL");
Thread.Sleep(500);
}
});
return true;
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
return false;
}
}
Upvotes: 0
Reputation: 169150
You cannot both update the UI and copy files on the same thread simultaneously.
You should either perform the copy on a background thread or use an asynchronous API, e.g.:
public async Task<bool> CopyFiles(string destDir, List<MyFile> lstFiles)
{
try
{
int counter = 0;
foreach (MyFile f in lstFiles)
{
f.jobStatus = "Copying";
using (Stream source = File.Open(f.fullFileName))
using (Stream destination = System.IO.File.Create(Path.Combine(destDir, f.fileName)))
await source.CopyToAsync(destination);
f.jobStatus = "Finished";
counter += 1;
Console.WriteLine("M: " + DateTime.Now.ToString("hh:mm:ss") + " " + counter);
Messenger.Default.Send(counter, "MODEL");
}
return true;
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
return false;
}
}
Note that you have to modify the signature of your method to be able to use the async
/await
keywords that were introduced in the .NET Framework 4.5 and C#5.
You should also await async methods "all the way":
private Task CopyFile()
{
var fh = new FileHelper();
if (await fh.CopyFiles(@"C:\Test\Destination\", fileList.ToList()))
Messenger.Default.Send("Files copying finished", "VM");
else
Messenger.Default.Send("Files copying failed", "VM");
}
private async void Action_Job(object tag)
{
if (tag == null || string.IsNullOrEmpty(tag.ToString()))
return;
switch (tag.ToString())
{
case "GET": GetFile(); break;
case "COPY": await CopyFile(); break;
}
}
Upvotes: 1