[原]WPF編程常常遇到一個問題:編程
某個數組己綁定到主界面某控件中,而後在後臺程序中須要對數組增(減)數據,而後程序就會報錯,數組
程序提示:該類型的CollectionView 不支持從調度程序線程之外的線程對其SourceCollection進行的更改。安全
以下圖所示:異步
既然不能這樣操做,就得想一個辦法來解決,如今先把把出現錯誤的程序所有列出來,而後再來根據解決辦法進行修改,ide
本測試程序先建一個學生類:測試
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace WPF_test { public class student : INotifyPropertyChanged { //定義數據更改事件通知和更改的方法 public event PropertyChangedEventHandler PropertyChanged; public void up(string s) { if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(s)); } } //姓名 string _name; public string name { get { return _name; } set { _name = value; up("name"); } } //學號 string _id; public string id { get { return _id; } set { _id = value; up("name"); } } public student(string _id, string _name) { id = _id; name = _name; } public student() { } } }
主窗口xaml代碼(將students數組綁定到主界面中):大數據
<Window x:Class="WPF_test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:zz="clr-namespace:WPF_test" Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" > <Window.Resources > <zz:VisibilityConverter x:Key="VisibilityConverter"/> </Window.Resources> <DockPanel > <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Margin="0 10 "> <Button Content="出錯測試" Margin="20 0" Click="Button_Click" /> <Button Content="正確測試" Margin="20 0" Click="Button_Click_1" /> </StackPanel> <Grid> <!-- 頂層等待動畫--> <Grid Name="g1" Panel.ZIndex="2" Visibility="{Binding isWorking, Converter={StaticResource VisibilityConverter}}"> <Border Background="Black" Height="100" BorderBrush="Gold" Opacity="0.7" BorderThickness="1"> <StackPanel > <TextBlock Text="請稍等" HorizontalAlignment="Center" Foreground="White" Margin="0 10 0 0"></TextBlock> <ProgressBar IsIndeterminate="True" Height="25" Margin="20"></ProgressBar> </StackPanel> </Border> </Grid> <!-- 底層數據顯示--> <ListView Name="listview1"> <ListView.View> <GridView > <GridViewColumn Header="學號" Width="80" DisplayMemberBinding="{Binding id}"/> <GridViewColumn Header="姓名" Width="150" DisplayMemberBinding="{Binding name}"/> </GridView> </ListView.View> </ListView> </Grid> </DockPanel> </Window>
等待動畫類代碼:動畫
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; namespace WPF_test { class witeMe:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void up(string s) { if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(s)); } } //工做提示狀態 private bool _isWorking = false; public bool isWorking { get { return _isWorking; } set { _isWorking = value; up("isWorking"); } } } }
主窗口核心代碼:this
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Shapes; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; namespace WPF_test { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } ObservableCollection<student> students = new ObservableCollection<student>(); witeMe wm = new witeMe(); private void Window_Loaded(object sender, RoutedEventArgs e) { students.Add(new student("1號","張三")); students.Add(new student("2號", "李四")); listview1.ItemsSource = students; g1.DataContext = wm; } //出錯測試 private void Button_Click(object sender, RoutedEventArgs e) { students.Clear(); Task tk = new Task(() => { Action<student, bool> ac1 = (x, y) => students.Add(x); createStudents(ac1); }); tk.Start(); tk.ContinueWith((t) => MessageBox.Show("結束")); } //正確測試 private void Button_Click_1(object sender, RoutedEventArgs e) { students.Clear(); wm.isWorking = true; Task tk = new Task(() => { Action<student, bool> ac2 = (x, y) => addData(x, y); createStudents(ac2); }); tk.Start(); } private void addData(student s, bool k) { ThreadPool.QueueUserWorkItem(delegate { this.Dispatcher.Invoke(new Action(() => { students.Add(s); if (k == true) wm.isWorking = false; // students.Insert(0, s);//這樣也會很卡 //listview1.ScrollIntoView(s);//這個不行,大數據時真會卡死了 }), null); }); } //建立學生數組 private void createStudents(Action<student, bool> _ac) { for (int i = 0; i < 100000; i++) { student s = new student(); s.id = (i + 1).ToString(); s.name = "小顆豆" + (i + 1).ToString(); if (i < 99999) _ac(s, false); else _ac(s, true); } } } }
程序運行時:點擊"錯誤測試"就會出現文章前邊的錯誤圖示,點擊"正確測試"出現下圖,一切正常:spa
正確測試時綁定的數組邊修改,畫面邊顯示,並且主窗口也沒有卡死,鼠標也能拖動窗口,基本能達到目的了,下面分析一下代碼是如何解決的:
//建立學生數組
private void createStudents(Action<student, bool> _ac)
{
for (int i = 0; i < 100000; i++)
{
student s = new student();
s.id = (i + 1).ToString();
s.name = "小顆豆" + (i + 1).ToString();
if (i < 99999)
_ac(s, false);
else
_ac(s, true);
}
}
在以上代碼中每增長一個學生成員到數組中都是經過_ac(s,bool)委託進行的,
委託的定義是在異步線程中定義好的.便是ac2
Task tk = new Task(() =>
{
Action<student, bool> ac2 = (x, y) => addData(x, y);
createStudents(ac2);
});
tk.Start();
異步Task裏,先定義了委託,每增長一個數組成員時委託addData方法在主界面調用者線程中由線程池去操做便可解決外線程不能更改數組的問題:
private void addData(student s, bool k)
{
ThreadPool.QueueUserWorkItem(delegate
{
this.Dispatcher.Invoke(new Action(() =>
{
students.Add(s);
if (k == true)
wm.isWorking = false;
// students.Insert(0, s);//這樣會很卡,若是數據量小時則會顯得很流暢
//listview1.ScrollIntoView(s);//這個不行,小數據無所謂,大數據時真會卡死界面了
}), null);
});
}
上邊的代碼中,
this.Dispatcher.Invoke(new Action(() =>
{
students.Add(s);
}),null);這裏是關鍵的解決辦法,主窗體是主線程建立的,每一個線程都有一個惟一的調度員,咱們的工做就是命令調度員去作相應的工做,這裏咱們就至關於命令主窗體的線程調度員去增長數組成員,這樣作線程是安全的,不會再有錯誤提示。
以上是本人測試的例子,不足之處請批評指正,高手請飄過。
~~~~~~~~~ 給昨天寫的內容仍是再補充一下:~~~~~~~~~~~~~~~~~~
補充:
以上例程爲了簡化沒有寫成MVVM,若是寫在MVVM的方式略有點不一樣,
代碼以下:
//主界面代碼 <Window x:Class="WPF_test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:zz="clr-namespace:WPF_test" Title="MainWindow" Height="350" Width="525" > <Window.Resources > <zz:VisibilityConverter x:Key="VisibilityConverter"/> </Window.Resources> <DockPanel > <Button DockPanel.Dock="Bottom" Content="MVVM增長數組成員測試" Margin="20 0" Command="{Binding AddStudents}" /> <Grid> <!-- 頂層等待動畫--> <Grid Name="g1" Panel.ZIndex="2" Visibility="{Binding myModel.StudentMaster.isWorking, Converter={StaticResource VisibilityConverter}}"> <Border Background="Black" Height="100" BorderBrush="Gold" Opacity="0.7" BorderThickness="1"> <StackPanel > <TextBlock Text="請稍等" HorizontalAlignment="Center" Foreground="White" Margin="0 10 0 0"></TextBlock> <ProgressBar IsIndeterminate="True" Height="25" Margin="20"></ProgressBar> </StackPanel> </Border> </Grid> <!-- 底層數據顯示--> <ListView Name="listview1" ItemsSource="{Binding myModel.StudentMaster.students}"> <ListView.View> <GridView > <GridViewColumn Header="學號" Width="80" DisplayMemberBinding="{Binding id}"/> <GridViewColumn Header="姓名" Width="150" DisplayMemberBinding="{Binding name}"/> </GridView> </ListView.View> </ListView> </Grid> </DockPanel> </Window> //主界面後臺代碼: namespace WPF_test { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { viewModel vm = new viewModel(); public MainWindow() { this.DataContext = vm; InitializeComponent(); } } } //NotifyUp.cs namespace WPF_test { public class NotifyUp : INotifyPropertyChanged { //定義數據更改事件通知和更改的方法 public event PropertyChangedEventHandler PropertyChanged; public void up(string s) { if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(s)); } } } } //Model代碼model.cs: namespace WPF_test { public class model : NotifyUp { //學生管理類 studentMaster _studentMaster = new studentMaster(); public studentMaster StudentMaster { get { return _studentMaster; } set { _studentMaster = value; } } } } //student.cs學生類代碼: namespace WPF_test { public class student : NotifyUp { //姓名 string _name; public string name { get { return _name; } set { _name = value; up("name"); } } //學號 string _id; public string id { get { return _id; } set { _id = value; up("id"); } } public student(string _id, string _name) { id = _id; name = _name; } public student() { } } } //studentMaster.cs學生管理類代碼: namespace WPF_test { public class studentMaster : NotifyUp { ObservableCollection<student> _students = new ObservableCollection<student>(); public ObservableCollection<student> students { get { return _students; } set { _students = value; up("students"); } } //工做提示狀態 private bool _isWorking = false; public bool isWorking { get { return _isWorking; } set { _isWorking = value; up("isWorking"); } } public studentMaster() { load(); } private void load() { students.Add(new student("1號", "張三")); students.Add(new student("2號", "李四")); } //異步線程增長學生成員 public void addStudent() { students.Clear(); isWorking = true; Task tk = new Task(() => { Action<student, bool> ac2 = (x, y) => addData(x, y); createStudents(ac2); }); tk.Start(); } /// <summary> //主窗口調度線程增長數組成員 /// </summary> /// <param name="s">學生成員</param> /// <param name="isend">是否結束</param> private void addData(student s, bool isend) { ThreadPool.QueueUserWorkItem(delegate { System.Threading.SynchronizationContext.SetSynchronizationContext(new System.Windows.Threading.DispatcherSynchronizationContext(System.Windows.Application.Current.Dispatcher)); System.Threading.SynchronizationContext.Current.Send(pl => { students.Add(s);//students.Insert (0,s);//從數組前邊加進成員,界面仍是比較卡 if (isend) isWorking = false; }, null); }); } //建立學生數組 private void createStudents(Action<student, bool> _ac) { for (int i = 0; i < 100000; i++) { student s = new student(); s.id = (i + 1).ToString(); s.name = "小顆豆" + (i + 1).ToString(); if (i < 99999) _ac(s, false); else _ac(s, true); } } } }
以上代碼經過測試.