0

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

我重新创建了这个示例以显示我在另一个程序中遇到的错误,我刚刚发现崩溃实际上与滚动条有关。如果我将显示数据的数量更改为较低的数字以便没有滚动条,则代码不会崩溃。

4

1 回答 1

0

对视图模型的以下更改解决了该问题。

我现在正在使用 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"));
        }
    }
}
于 2012-07-30T16:00:57.133 回答