【问题标题】:WPF DataGrid eats all the exceptionsWPF DataGrid 吃掉所有异常
【发布时间】:2013-12-05 02:34:28
【问题描述】:

我有一个 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。我正在使用 SqlLite 数据库,因为它是我在这台机器上安装的。但我很确定使用的数据库与这个问题无关,使用其他任何东西的结果都是一样的。

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>

请注意,我在那里也有一个按钮,它的点击处理程序也会抛出。在正确显示“异常捕获”消息框的情况下,这可以按预期工作。

另外请注意,我正在使用禁用异常过滤的自定义 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 抛出的异常被静默吃掉。这真的不太好。如果某些异常会被默默地吃掉,那么这将是对任何健壮性尝试的嘲弄。

【问题讨论】:

    标签: c# wpf datagrid exception-handling


    【解决方案1】:

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

    【讨论】:

    • 谢谢,挖得好。关于我没有谷歌的一件事是处理程序本身的名称。
    • 不幸的是,从 DataTable.RowChanging 抛出的异常似乎也被吃掉了。更重要的是,尽管您可以使用 ContinueUpdateOnError 在失败的行上设置 RowError,但 WPF DataGrid 似乎不会触发在编辑/更新之后发生的任何事件,您可以在其中实际检查 RowError。所以总而言之,这是一场噩梦,我仍然没有解决方案。因此,尽管您的回复很有帮助,但我将取消勾选它作为解决方案,看看是否有实际的解决方案出现。
    • 我认为您可以使用 HasErrors(msdn.microsoft.com/en-us/library/…) 属性来检查是否有任何行有错误,如果 HasErrors 为真,您必须检查 GetErrors 返回的每一行以检查错误。 msdn.microsoft.com/en-us/library/k3877412(v=vs.110).aspx
    • 但是更新后会触发什么事件,您可以在其中检查属性?
    猜你喜欢
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 1970-01-01
    • 2012-10-04
    • 2010-10-19
    • 2011-07-12
    • 1970-01-01
    相关资源
    最近更新 更多