2012-07-27 93 views
0

我有一个视图,其中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:

我重建这个例子来说明错误我已经在另一个程序,我只是发现了什么崩溃实际上是与滚动条相关。如果我将显示数据的数量更改为较低的数字,以便没有滚动条,则代码不会崩溃。

+3

你有免疫吗?如果是这样,请编辑您的问题,并添加exeption消息和stacktrace。 – GameScripting 2012-07-27 17:49:25

+1

哪条线会崩溃? – Paparazzi 2012-07-27 19:48:32

+1

通过设计后台线程无法访问UI对象。我怀疑它试图更新UI时崩溃了。 UI不会每次检查正确的线程,因此您可能需要进行一些更新以使您相信这不是问题。但它确实很快,而且不好。不是肯定地说这是问题,但我可以告诉我有类似的症状类似的问题。我所做的就是让UI RO在后台处理副本,然后将UI绑定到回调中的处理副本并制作UI RW。 – Paparazzi 2012-07-28 20:47:34

回答

0

对viewmodel进行以下更改可解决此问题。

我现在正在使用wpf的一个副本来处理并且只记录它们应该发生的情况。这个代码在改进机制不完善方面存在问题 - 但这超出了这个问题的范围。

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")); 
     } 
    } 
}