が含まれているビューがありますDataGrid
。バックグラウンドオブジェクトのにDataContext
アクセスできる場所としてのViewModel 。DataTable
バックグラウンドオブジェクトは、と連携しDataTable
て更新を維持する必要があります。ユーザーはそれを変更することも許可されていDataTable
ます。
コピーを作成する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()});
}
}
}
}
このマルチスレッドのクラッシュを防ぐにはどうすればよいですか?
編集:
このコードがクラッシュする理由が複数あるかどうかはわかりません。しかし、私はクラッシュする1つの理由に関するいくつかの情報を収集するために最善を尽くしました:
App.g.csのNullpointer例外-自動生成された部分。デバッガーはそれに踏み込みません-それで私はそれがクラッシュするラインについて何も言うことができません。
これが例外の詳細です。ドイツ人には申し訳ありません。
System.NullReferenceException wurde nicht behandelt.
Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Source=PresentationFramework
InnerException:
Stacktraceには「ExternerCode」のみが表示されるため、トレースするスタックはありません。
問題は、WPFがクラッシュすることです-私のコードはそれを処理できます...どういうわけか私はWPFをカプセル化してクラッシュしないようにする必要があります、それを行う1つの方法はDataTableをコピーすることです-しかしそれから私はそのテーブルを書き戻す能力を失います何かが編集されたとき、setterは呼び出されません。
編集#2:
この例を再作成して、別のプログラムで発生したエラーを示しました。クラッシュしたものが実際にはスクロールバーに関連していることがわかりました。表示されるデータの量を小さい数値に変更してスクロールバーがないようにすると、コードはクラッシュしません。