我有一個視圖,其中DataGrid
。 一個ViewModel作爲DataContext
我可以在後臺對象中訪問DataTable
。 背景對象必須與DataTable
一起使用並保持更新。 該用戶也被允許對該DataTable
進行更改。WPF DataGrid多線程崩潰
如果我創建了DataTable
的副本,它會停止崩潰,但用戶明顯不會處理數據。
如果我將訪問權限留給用戶打開,程序肯定會崩潰。
這裏是一個簡短的程序,這將崩潰:
app.cs
public partial class App : Application
{
public App()
{
SomeBackgroundThing background = new SomeBackgroundThing();
MainWindowViewModel viewmodel = new MainWindowViewModel(background);
MainWindowView view = new MainWindowView(viewmodel);
view.Show();
}
}
主要XAML
<Window x:Class="NullPointerDataGrid.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid Name="datagrid" ItemsSource="{Binding Path=table, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid>
</Window>
和程序代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Data;
using System.Diagnostics;
using System.Timers;
using System.ComponentModel;
namespace NullPointerDataGrid
{
public partial class MainWindowView : Window
{
public MainWindowView(MainWindowViewModel model)
{
DataContext = model;
InitializeComponent();
datagrid.Loaded += new RoutedEventHandler(ScrollToBottom);
datagrid.AutoGeneratedColumns += new EventHandler(StarSizeLastRow);
}
void ScrollToBottom(object sender, RoutedEventArgs e)
{
Debug.WriteLine("TableGrid_ScrollToBottom");
if (datagrid.Items.Count > 0)
{
var border = VisualTreeHelper.GetChild(datagrid, 0) as Decorator;
if (border != null)
{
var scroll = border.Child as ScrollViewer;
if (scroll != null) scroll.ScrollToEnd();
}
}
}
void StarSizeLastRow(object sender, EventArgs e)
{
Debug.WriteLine("TableGrid_StarSizeLastColumn");
try
{
datagrid.Columns[datagrid.Columns.Count - 1].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
catch { }
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private SomeBackgroundThing thing;
public DataTable table
{
get
{
lock (thing.table)
{
//DataTable wpfcopy = thing.table.Copy();
return thing.table;
};
}
set
{
Debug.Write("This never happens");
}
}
public MainWindowViewModel(SomeBackgroundThing thing)
{
this.thing = thing;
thing.Changed += new EventHandler(thing_Changed);
}
void thing_Changed(object sender, EventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("table"));
}
}
}
public class SomeBackgroundThing : IDisposable
{
public DataTable table;
private DataTable tablecopy;
private System.Timers.Timer timer, slowrowchanger;
public event EventHandler Changed = new EventHandler((o, e) => { ;});
protected void CallChanged(object sender, EventArgs e)
{
Changed(sender, e);
}
public SomeBackgroundThing()
{
CreateTable();
UpdateB(this, null);
tablecopy = table.Copy();
InitAndStartTimer(1);
}
#region timer
private void UpdateA()
{
Boolean haschanged = false;
DataTable newcopy = table.Copy(); ;
if (newcopy.Rows.Count != tablecopy.Rows.Count)
{
Debug.WriteLine("Different ammount of rows");
haschanged = true;
}
else if (newcopy.Columns.Count != tablecopy.Columns.Count)
{
Debug.WriteLine("Different ammount of columns");
haschanged = true;
}
else
{
for (int i = 0; i < newcopy.Rows.Count; i++)
{
for (int j = 0; j < newcopy.Columns.Count; j++)
{
if (newcopy.Rows[i][j].ToString() != tablecopy.Rows[i][j].ToString())
{
Debug.WriteLine(String.Format(
"Element [{0}/{1}]: {2} is different from {3}",
i, j, newcopy.Rows[i][j], tablecopy.Rows[i][j]
));
haschanged = true;
}
if (haschanged) break;
}
if (haschanged) break;
}
}
if (haschanged)
{
tablecopy = newcopy;
}
}
private void InitAndStartTimer(int interval)
{
timer = new System.Timers.Timer();
timer.Interval = interval;
timer.AutoReset = true;
timer.Elapsed += new ElapsedEventHandler((s, e) =>
{
UpdateA();
});
timer.Enabled = true;
slowrowchanger = new System.Timers.Timer();
slowrowchanger.Interval = 3000;
slowrowchanger.AutoReset = true;
slowrowchanger.Elapsed += new ElapsedEventHandler((s, e) =>
{
UpdateB(null, null);
});
slowrowchanger.Enabled = true;
}
public void Dispose()
{
timer.Enabled = false;
slowrowchanger.Enabled = false;
timer.Dispose();
slowrowchanger.Dispose();
}
#endregion
#region editlastrow
void UpdateB(object sender, EventArgs e)
{
Random rnd = new Random();
List<String> cells = new List<string>{
"The SAME",
rnd.Next(0,100).ToString(),
rnd.ToString(),
rnd.NextDouble().ToString()};
lock (table)
{
OverwriteOrAppendLastRow(ref table, cells);
table.AcceptChanges();
}
CallChanged(this, null);
}
private void OverwriteOrAppendLastRow(ref DataTable table, List<string> newrow)
{
if (table.Rows.Count == 0) CreteEmptyRow(ref table);
if (newrow[0].ToString() != table.Rows[table.Rows.Count - 1][0].ToString())
{
Debug.WriteLine(String.Format("Creating row because '{0}' is different from '{1}'", newrow[0], table.Rows[table.Rows.Count - 1][0]));
CreteEmptyRow(ref table);
}
OverwriteLastRow(ref table, newrow);
}
private void OverwriteLastRow(ref DataTable table, List<string> newrow)
{
for (int i = 0; i < newrow.Count() && i < table.Columns.Count; i++)
{
table.Rows[table.Rows.Count - 1][i] = newrow[i];
}
}
private void CreteEmptyRow(ref DataTable table)
{
table.Rows.Add(new String[table.Columns.Count]);
}
#endregion
private void CreateTable()
{
table = new DataTable();
table.Columns.Add("FirstCell", typeof(String));
table.Columns.Add("BananaCell", typeof(String));
table.Columns.Add("CherryCell", typeof(String));
table.Columns.Add("Blue", typeof(String));
Random rnd = new Random();
for (int i = 0; i < 145; i++)
{
table.Rows.Add(new String[]{
rnd.Next().ToString(),
rnd.Next(0,i+1).ToString(),
rnd.ToString(),
rnd.NextDouble().ToString()});
}
}
}
}
哪有我停止這個多人ead崩潰了?
編輯:
我不知道是否有多於一個原因造成的代碼崩潰。但我盡我所能收集了一些關於崩潰原因的信息:
App.g.cs中的Nullpointer異常 - 自動生成的部分。調試器不會進入它 - 所以我不能說任何關於它崩潰的線。
這是異常詳細信息,對德國人抱歉。
System.NullReferenceException wurde nicht behandelt.
Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Source=PresentationFramework
InnerException:
Stacktrace只顯示「Externer Code」,因此沒有要跟蹤的堆棧。我的代碼可以處理它...不知何故,我需要封裝WPF所以它不會崩潰,一種方法是複製DataTable - 但後來我放棄了寫回來的能力該表格自其setter不被調用時,已編輯的東西。
編輯#2:
我重建這個例子來說明錯誤我已經在另一個程序,我只是發現了什麼崩潰實際上是與滾動條相關。如果我將顯示數據的數量更改爲較低的數字,以便沒有滾動條,則代碼不會崩潰。
你有免疫嗎?如果是這樣,請編輯您的問題,並添加exeption消息和stacktrace。 – GameScripting 2012-07-27 17:49:25
哪條線會崩潰? – Paparazzi 2012-07-27 19:48:32
通過設計後臺線程無法訪問UI對象。我懷疑它試圖更新UI時崩潰了。 UI不會每次檢查正確的線程,因此您可能需要進行一些更新以使您相信這不是問題。但它確實很快,而且不好。不是肯定地說這是問題,但我可以告訴我有類似的症狀類似的問題。我所做的就是讓UI RO在後臺處理副本,然後將UI綁定到回調中的處理副本並製作UI RW。 – Paparazzi 2012-07-28 20:47:34