2012-07-27 155 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")); 
     } 
    } 
}