我有一个视图,DataGrid
里面有一个。DataContext
我可以DataTable
在后台对象中访问的ViewModel 。背景对象必须使用DataTable
并保持更新。还允许用户对其进行更改DataTable
。
如果我创建它的副本,DataTable
它会停止崩溃,但用户显然没有处理数据。
如果我为用户保留访问权限,则程序不可避免地崩溃了。
这是一个会崩溃的简短程序:
应用程序.cs
public partial class App : Application
{
public App()
{
SomeBackgroundThing background = new SomeBackgroundThing();
MainWindowViewModel viewmodel = new MainWindowViewModel(background);
MainWindowView view = new MainWindowView(viewmodel);
view.Show();
}
}
主文件
<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()});
}
}
}
}
我怎样才能阻止这个多线程崩溃?
编辑:
我不知道这段代码崩溃的原因是否不止一个。但我尽我所能收集有关崩溃原因的一些信息:
App.g.cs 中的 Nullpointer 异常 - 自动生成的部分。调试器不会介入它 - 所以我不能说它崩溃的行。
这是异常详细信息,对德国人感到抱歉。
System.NullReferenceException wurde nicht behandelt.
Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Source=PresentationFramework
InnerException:
Stacktrace 仅显示“外部代码”,因此没有要跟踪的堆栈。
问题是 WPF 崩溃了——我的代码可以处理它……不知何故我需要封装 WPF,这样它就不会崩溃,一种方法是复制 DataTable——但后来我失去了写回该表的能力,因为它编辑某些内容时不会调用 setter。
编辑#2:
我重新创建了这个示例以显示我在另一个程序中遇到的错误,我刚刚发现崩溃实际上与滚动条有关。如果我将显示数据的数量更改为较低的数字以便没有滚动条,则代码不会崩溃。