WPF DataGrid会吃掉所有异常

时间:2013-11-20 21:13:09

标签: c# wpf datagrid exception-handling

我有一个WPF应用程序,它打开一个数据库表,用数据库中的表的内容填充DataTable,然后使用System.Windows.Controls.DataGrid提供它的视图。更新数据库是为了响应DataGrid中的用户输入。

用于此演示的数据库中的表没有主键,因此在插入数据库时​​工作正常,尝试更新现有值将引发异常。这是预期的,我知道如何解决这个问题,这不是问题所在。问题是,在DataAdapter上调用Update时抛出的异常会被静默吃掉。我需要这个例外来将堆栈传播到可以合理处理的位置。这段代码只是一个演示,在我的实际代码中,生成DataTable的程序集(包含发生异常的处理程序)是一个可重用的低级程序集,没有UI依赖。

以下是App.xaml.cs中的异常捕获处理程序

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DispatcherUnhandledException += OnCurrent_DispatcherUnhandledException;
        AppDomain.CurrentDomain.UnhandledException += OnCurrentDomain_UnhandledException;
    }

    private void OnCurrent_DispatcherUnhandledException(object sender,
        DispatcherUnhandledExceptionEventArgs args)
    {
        MessageBox.Show(args.Exception.Message, "Exception Caught");
        args.Handled = true;
    }

    private void OnCurrentDomain_UnhandledException(object sender,
        UnhandledExceptionEventArgs args)
    {
        MessageBox.Show(args.ExceptionObject.ToString(), "Exception Caught");
    }
}

这是我的MainWindow.xaml.cs。我正在使用SqLite数据库,因为它是我在这台机器上安装的。但我很确定使用的数据库与此问题无关,使用其他任何结果都是相同的。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitLocalDatabase();
        InitializeComponent();
    }

    public DataTable Table { get; private set; }

    private void InitLocalDatabase()
    {
        string currentDirectory = Path.GetDirectoryName(
            Assembly.GetExecutingAssembly().Location);
        string databaseName = Path.Combine(currentDirectory, "testdb.sqlite");
        string connectionString = "Data Source=" + databaseName + ";";

        if (!File.Exists(databaseName))
        {
            // Create and open database.
            SQLiteConnection.CreateFile(databaseName);
            _connection = new SQLiteConnection(connectionString);
            _connection.Open();

            // Create table in database.
            const string createTable = "create table Table1 (Column1 int, Column2 int)";
            using (SQLiteCommand cmd = new SQLiteCommand(createTable, _connection))
            {
                cmd.ExecuteNonQuery();
            }

            // Add data to table.
            const string addRow = "insert into Table1 values ({0}, {1})";
            for (int i = 0; i < 5; i += 2)
            {
                using (SQLiteCommand cmd = new SQLiteCommand(
                    string.Format(addRow, i, i + 1), _connection))
                {
                    cmd.ExecuteNonQuery();
                }
            }
        }
        else
        {
            _connection = new SQLiteConnection(connectionString);
        }

        // Create the DataAdapter and DataTable.
        _dataAdapter = new SQLiteDataAdapter("select * from Table1", _connection);
        SQLiteCommandBuilder cb = new SQLiteCommandBuilder(_dataAdapter);
        Table = new DataTable();
        _dataAdapter.Fill(Table);
        Table.RowChanged += OnDataTable_RowChanged;
    }

    private void OnDataTable_RowChanged(object sender, DataRowChangeEventArgs args)
    {
        try
        {
            Debug.Assert(Dispatcher.CheckAccess()); // Verify UI thread.
            _dataAdapter.Update(Table);
        }
        catch (Exception)
        {
            MessageBox.Show("Throwing exception");
            throw new Exception("Shit happens"); // This is eaten.
        }
    }

    private void OnButton_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Throwing exception");
        throw new Exception("Shit happens!!"); // This is not eaten.
    }

    protected override void OnClosing(CancelEventArgs args)
    {
        base.OnClosing(args);
        if(_connection != null)
        {
            _connection.Dispose();
            _connection = null;
        }
    }

    private SQLiteConnection _connection;
    private SQLiteDataAdapter _dataAdapter;
}

我的MainWindow.xaml只是

<StackPanel>
    <Button Click="OnButton_Click">Button 1</Button>
    <DataGrid Height="200" ItemsSource="{local:ThrowBinding Table, ElementName=_this}"/>
</StackPanel>

注意我也有一个Button也点击处理程序也会抛出。这可以正常工作,正确显示“Exception Caught”MessageBox。

另请注意,我正在使用禁用异常过滤的自定义Binding子类。代码如下。

public class ThrowBinding : Binding
{
    public ThrowBinding()
    {
        Init();
    }

    public ThrowBinding(string path)
        : base(path)
    {
        Init();
    }

    private void Init()
    {
        UpdateSourceExceptionFilter = _exceptionFilter;
        ValidationRules.Add(_validationRule);
    }

    private static object ExceptionFilter(object bindingExpression, Exception e)
    {
        throw e;
    }

    private static readonly UpdateSourceExceptionFilterCallback _exceptionFilter = ExceptionFilter;
    private static readonly ExceptionValidationRule _validationRule = new ExceptionValidationRule();
}

我使用AnyCpu和x86配置构建,结果相同,OnDataTable_RowChanged在尝试编辑现有值时抛出的异常是静默使用的。这真的不太好。如果某些例外情况会被悄悄地吃掉,那么这将是对任何鲁棒性尝试的嘲弄。

1 个答案:

答案 0 :(得分:1)

似乎有一个错误,你实际上无法捕获从RowChanged事件抛出的异常。 Here是他们的“按设计”解释。 你可以做的是使用RowChanging事件或尝试设置ContinueUpdateOnError,然后使用GetErrors方法检查你的行是否有更新时发生的错误。

相关问题