ObservableCollection 更改后 ListView 未更新
ListView not updating after ObservableCollection changed
我已经尝试了几个小时来让带有 ObservableCollection 的 ListView 正常工作。然而,没有运气。我也在这里和那里阅读了一些帖子并尝试匹配但仍然没有用。请指点一下哪里出错了。
基本上,我想做的是将逻辑从 VM 拆分到 Helper class。 class 中的逻辑将更新数据,但 VM 并不知道它。
我的问题是在复制文件功能中,作业状态不会更改视图数据。我尝试了 Messenger.Default.Send(来自助手)和 Messenger 注册(在 VM 中),以接受更改,但仍然没有运气。
顺便说一下,我正在使用 MVVM Light、WPF、C#。
这是我的模型代码。
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); }
}
}
这是我的助手代码。
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;
}
}
}
这是我的 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");
}
}
这是我的 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>
我阅读了一些帖子,它说 "change",ObservableCollection 不会反映对 View 的更改。所以,我遵循那些帖子解决方案(在模型 class 中使用 Notify change),但对我不起作用。
对我来说,我的个人文件很大,所以我可以看到我的 VM 上没有更新。
如果您使用小文件进行测试,您将看不到差异。
我尝试使用 Messenger 方法,它也没有在 View 上更新,但我的 VM 可以毫无问题地接受传入消息。
您不能在同一线程上同时更新 UI 和复制文件。
您应该在后台线程上执行复制或使用异步 API,例如:
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;
}
}
请注意,您必须修改方法的签名才能使用 .NET Framework 4.5 和 C#5 中引入的 async
/await
关键字。
你还应该等待异步方法"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;
}
}
感谢 Clemens 和 mm8,我设法将其更改为异步并等待 UI 更新。
我认为我的实现方式仍然合理(与 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;
}
}
我已经尝试了几个小时来让带有 ObservableCollection 的 ListView 正常工作。然而,没有运气。我也在这里和那里阅读了一些帖子并尝试匹配但仍然没有用。请指点一下哪里出错了。
基本上,我想做的是将逻辑从 VM 拆分到 Helper class。 class 中的逻辑将更新数据,但 VM 并不知道它。
我的问题是在复制文件功能中,作业状态不会更改视图数据。我尝试了 Messenger.Default.Send(来自助手)和 Messenger 注册(在 VM 中),以接受更改,但仍然没有运气。
顺便说一下,我正在使用 MVVM Light、WPF、C#。
这是我的模型代码。
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); }
}
}
这是我的助手代码。
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;
}
}
}
这是我的 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");
}
}
这是我的 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>
我阅读了一些帖子,它说 "change",ObservableCollection 不会反映对 View 的更改。所以,我遵循那些帖子解决方案(在模型 class 中使用 Notify change),但对我不起作用。
对我来说,我的个人文件很大,所以我可以看到我的 VM 上没有更新。
如果您使用小文件进行测试,您将看不到差异。
我尝试使用 Messenger 方法,它也没有在 View 上更新,但我的 VM 可以毫无问题地接受传入消息。
您不能在同一线程上同时更新 UI 和复制文件。
您应该在后台线程上执行复制或使用异步 API,例如:
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;
}
}
请注意,您必须修改方法的签名才能使用 .NET Framework 4.5 和 C#5 中引入的 async
/await
关键字。
你还应该等待异步方法"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;
}
}
感谢 Clemens 和 mm8,我设法将其更改为异步并等待 UI 更新。
我认为我的实现方式仍然合理(与 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;
}
}