WPF DataGrid多线程崩溃 [英] WPF DataGrid Multithreading Crash
问题描述
我有一个 DataGrid
的视图。
ViewModel as DataContext
我可以在后台对象中访问 DataTable
。
背景对象必须使用 DataTable
并保持更新。
还允许用户对 DataTable
进行更改。
I have a View with a DataGrid
in it.
A ViewModel as DataContext
where i can access a DataTable
in a background object.
The background object has to work with the DataTable
and keep it updated.
The user has also be allowed to make changes to that DataTable
.
如果我创建一个副本的 DataTable
它会停止崩溃,但用户无法正常处理数据。
If i create a copy of the DataTable
it stops crashing but the user is obviousely not working on the data.
如果我离开访问权限对于用户,该程序不可避免地崩溃。
If i leave access open for the user the program crashed inevitabely.
这是一个简短的程序,将崩溃:
Here is a short program that will crash:
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()});
}
}
}
}
我如何阻止这个多线程崩溃?
How can i stop this multithread crashing?
编辑:
我不知道这个代码有多个原因导致崩溃。但是我尽力收集一些关于崩溃原因的信息:
I don't know if there are more than one reasons for this code to crash. But i did my best to gather some information about one reason to crash:
App.g.cs中的Nullpointer异常 - 自动生成部分。调试器不会进入它 - 所以我不能说任何关于它崩溃的线路。
Nullpointer exception in App.g.cs - the autogenerated portion. The Debugger wont step into it - so i cant say anything about the line it crashes in.
这是异常详细信息,对于德国人来说是不好的。
Here is the Exception Detail, sorry for the German.
System.NullReferenceException wurde nicht behandelt.
Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Source=PresentationFramework
InnerException:
StackTrace只显示Externer Code所以没有堆栈跟踪。
The Stacktrace only shows "Externer Code" so no stack to trace.
事情是WPF崩溃 - 我的代码可以处理它...不知何故我需要胶囊WPF,所以它不会崩溃,一种方法这样做是为了复制DataTable - 但是我松开了写回该表的能力,因为在编辑某些东西时,它不会调用setter。
The thing is that WPF crashes - my code can handle it... somehow i need to capsule WPF so it wont crash, one way to do that is to copy the DataTable - but then i loose the ability to write back that table since its setter is not called when something has gotten edited.
编辑#2:
我重新创建了这个例子来显示我在另一个程序中出现的错误,我刚刚发现崩溃与滚动条实际有关。如果我将显示数据的最大数量更改为低数字,以便没有滚动条,代码将不会崩溃。
I recreated this example to show the error i have in another program and i just found out that what crashes is actually related with the scrollbar. If i change the ammount of displayed data to a low number so that there is no scrollbar, the code will not crash.
推荐答案
viewmodel的以下更改解决了这个问题。
The following change to the viewmodel solves the problem.
我现在正在使用一个wpf的副本来处理,只有注意到它们会发生的。这个代码有一个很差的改进机制的问题 - 但这超出了这个问题的范围。
I am now using a copy for wpf to work on and only note the canges should they occur. This code has an issue with the poorly refined change mechanism - but that is beyond the scope of this question.
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private SomeBackgroundThing thing;
private DataTable wpftable;
public DataTable table
{
get
{
lock (wpftable)
{
return wpftable;
}
}
set
{
lock (wpftable)
{
wpftable = value;
}
}
}
public MainWindowViewModel(SomeBackgroundThing thing)
{
wpftable = thing.table.Copy();
this.thing = thing;
thing.Changed += new EventHandler(thing_Changed);
}
void thing_Changed(object sender, EventArgs e)
{
if (PropertyChanged != null)
{
DataTable wpftablecopy = wpftable.Copy();
DataTable thintablecopy = thing.table.Copy();
int rowcount = wpftablecopy.Rows.Count;
for (int col = 0; col < 4; col++)
{
for (int row = 0; row < rowcount; row++)
{
if (wpftablecopy.Rows[row][col] != thintablecopy.Rows[row][col])
wpftable.Rows[row][col] = thintablecopy.Rows[row][col];
}
}
PropertyChanged(this, new PropertyChangedEventArgs("table"));
}
}
}
这篇关于WPF DataGrid多线程崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!