You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF ListView是非单选按钮存数据库遇空引用问题解决

解决WPF ListView单选按钮绑定与SQLite存储问题

你的空引用异常根源很明确:点击单选按钮时,ListView的SelectedItem并没有自动关联到当前点击的问题项,所以listviewQue.SelectedItem as Question返回null,访问q.que_id就抛出了NullReferenceException。另外还有两个隐性问题:所有单选按钮共用同一个GroupName,导致整个ListView只能选一个答案;SQL语句直接拼接字符串存在注入风险。下面一步步解决这些问题:

1. 修复单选按钮组的独立性问题

每个问题的"是/否"应该是独立的选项组,所以要把GroupName绑定到每个Question的唯一标识(比如que_id),这样不同问题的单选按钮不会互相干扰:

<ListView HorizontalAlignment="Left" x:Name="listviewQue" Background="Azure" ItemsSource="{Binding Questions}" ScrollViewer.CanContentScroll="False" SelectionMode="Multiple">
    <ListView.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBlock DockPanel.Dock="Top" Text="{Binding que_text}" Style="{StaticResource textblock_style}" />
                <!-- 绑定GroupName到当前问题的que_id,确保每组独立 -->
                <RadioButton GroupName="{Binding que_id}" IsChecked="{Binding Answer, Converter={StaticResource BooleanToStringConverter}, ConverterParameter=yes}" DockPanel.Dock="Left">yes</RadioButton>
                <RadioButton GroupName="{Binding que_id}" IsChecked="{Binding Answer, Converter={StaticResource BooleanToStringConverter}, ConverterParameter=no}" DockPanel.Dock="Right">no</RadioButton>
            </DockPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

2. 用数据绑定替代事件处理(WPF推荐方式)

放弃点击事件,给Question类添加Answer属性,通过值转换器绑定单选按钮的IsChecked状态,这样用户选择时自动更新属性,同时避免空引用问题:

第一步:添加值转换器

需要一个转换器把字符串类型的"yes"/"no"和单选按钮的bool状态做双向转换:

public class BooleanToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string currentAnswer = value as string;
        string targetValue = parameter as string;
        return currentAnswer == targetValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool isChecked = (bool)value;
        return isChecked ? parameter as string : null;
    }
}

记得在XAML的资源中声明这个转换器:

<Window.Resources>
    <local:BooleanToStringConverter x:Key="BooleanToStringConverter" />
</Window.Resources>

第二步:改造Question类

让Question实现INotifyPropertyChanged(WPF数据绑定必备),添加Answer属性,同时注入需要的上下文数据(SurveyId和EmployeeId):

namespace Version2._0
{
    public class Question : INotifyPropertyChanged
    {
        public int que_id { get; set; }
        public string que_text { get; set; }
        // 注入上下文数据:问卷ID和员工ID
        public string SurveyId { get; set; }
        public string EmployeeId { get; set; }

        private string _answer;
        public string Answer
        {
            get => _answer;
            set
            {
                if (_answer != value)
                {
                    _answer = value;
                    OnPropertyChanged(nameof(Answer));
                    // 当答案确定后,自动保存到数据库
                    if (!string.IsNullOrEmpty(_answer))
                    {
                        SaveResponseToDb();
                    }
                }
            }
        }

        private void SaveResponseToDb()
        {
            string result = SQLiteDataAccess.AddResponse(SurveyId, EmployeeId, que_id, Answer);
            if (result != SQLiteDataAccess.SUCCESS)
            {
                MessageBox.Show($"保存失败:{result}");
            }
        }

        // 实现INotifyPropertyChanged接口
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

3. 修复SQLite插入的安全与稳定性问题

直接拼接SQL字符串存在SQL注入风险,而且容易因为特殊字符导致语法错误,改用参数化查询:

public static string AddResponse(string sid, string eid, int qid, string answer)
{
    try
    {
        using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
        {
            // 参数化查询,避免注入和语法错误
            string query = @"INSERT INTO RESPONSE (SurveyId, EmployeeId, QuestionId, Answer) 
                             VALUES (@sid, @eid, @qid, @answer)";
            cnn.Execute(query, new { sid, eid, qid, answer });
        }
        return SUCCESS;
    }
    catch (Exception e)
    {
        return $"{FAILURE}: {e.Message}";
    }
}

4. 初始化问题集合时注入上下文数据

在加载Questions集合时,把surveryId和emp[empIndex].Id赋值给每个Question:

// 示例:假设你从数据库获取了问题列表
List<Question> questions = SQLiteDataAccess.GetAllQuestions();
foreach (var q in questions)
{
    q.SurveyId = surveryId;
    q.EmployeeId = emp[empIndex].Id;
}
// 绑定到ListView
listviewQue.ItemsSource = questions;

可选优化:批量提交答案

如果你不想每次选择都立即访问数据库,可以在用户完成所有选择后,遍历Questions集合,批量提交有答案的项:

// 批量保存按钮点击事件
private void SaveAllResponses_Click(object sender, RoutedEventArgs e)
{
    var responses = (listviewQue.ItemsSource as List<Question>)
                    .Where(q => !string.IsNullOrEmpty(q.Answer))
                    .Select(q => new { q.SurveyId, q.EmployeeId, q.que_id, q.Answer });

    using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
    {
        string query = @"INSERT INTO RESPONSE (SurveyId, EmployeeId, QuestionId, Answer) 
                         VALUES (@SurveyId, @EmployeeId, @que_id, @Answer)";
        cnn.Execute(query, responses);
    }
    MessageBox.Show("所有答案已保存!");
}

这样整个流程就可以正常工作,再也不会出现空引用异常,同时代码更符合WPF的MVVM设计思想,也更安全稳定。

内容的提问来源于stack exchange,提问作者la27

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:43:08