WPF ListView是非单选按钮存数据库遇空引用问题解决
你的空引用异常根源很明确:点击单选按钮时,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

