我正在使用Visual Studio中的C#WPF项目,我允许用户添加一个用户控件(下面的代码中有两个文本框),这会创建一个类(数据存储在用户中)控制,以便可以使用单个按钮创建多个用户控件)并将其添加到存储在主窗口文件中的列表中,以便稍后写入csv文件。当我按下按钮创建新的用户控件时,我收到以下错误:
未处理的类型' System.NullReferenceException'发生在MasterListErrorTest.exe
中附加信息:未将对象引用设置为对象的实例。
我创建了一个项目的简化版本,其中只包含重现错误所需的元素。这是我的所有代码,因此您可以直接插入并自行获取错误。我做错了什么?
MainWindow.xaml:
<Window x:Class="MasterListErrorTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MasterListErrorTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel x:Name="UserControlContainer" HorizontalAlignment="Left" Height="216" Margin="24,83,0,0" VerticalAlignment="Top" Width="471" Orientation="Horizontal"/>
<Button x:Name="CreateNewControl" Content="Create New" HorizontalAlignment="Left" Margin="76,37,0,0" VerticalAlignment="Top" Width="75" Click="CreateNewControl_Click"/>
<Button x:Name="GiveStringFromList" Content="Give String" HorizontalAlignment="Left" Margin="360,37,0,0" VerticalAlignment="Top" Width="75" Click="GiveStringFromList_Click"/>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MasterListErrorTest
{
public partial class MainWindow : Window
{
int MultipleTextBoxControlID = 1;
public MainWindow()
{
InitializeComponent();
}
public static class TextBoxControlList
{
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
UserControlContainer.Children.Add(newUserControl);
}
private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
{
foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in TextBoxControlList.MasterDataList)
{
List<string> userControlLine = new List<string>();
userControlLine.Add(textBoxPanel.Identifier.ToString());
userControlLine.Add(textBoxPanel.TextBox1Data);
userControlLine.Add(textBoxPanel.TextBox2Data);
MessageBox.Show(string.Join(",", userControlLine));
}
}
}
}
MultipleTextBoxControl.xaml:
<UserControl x:Class="MasterListErrorTest.MultipleTextBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MasterListErrorTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300">
<Grid>
<TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="0,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox1_TextChanged"/>
<TextBox x:Name="textBox2" HorizontalAlignment="Left" Height="23" Margin="153,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox2_TextChanged"/>
</Grid>
</UserControl>
MultipleTextBoxControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MasterListErrorTest
{
public partial class MultipleTextBoxControl : UserControl
{
TextBoxData newTextBoxGroup = new TextBoxData();
public MultipleTextBoxControl(int identifier)
{
InitializeComponent();
newTextBoxGroup.Identifier = identifier;
MainWindow.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);
}
public class TextBoxData
{
public int Identifier { get; set; }
public string TextBox1Data { get; set; }
public string TextBox2Data { get; set; }
public TextBoxData()
{
TextBox1Data = "Unchanged Textbox 1";
TextBox2Data = "Unchanged Textbox 2";
}
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
newTextBoxGroup.TextBox1Data = textBox1.Text;
}
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
newTextBoxGroup.TextBox2Data = textBox2.Text;
}
}
}
答案 0 :(得分:1)
如果在CreateNewControl_Click中的代码周围放置一个try / catch异常块,那么捕获的异常将为您提供有关正在发生的事情的更多信息。 StackTrace说:
at GuiTest.MultipleTextBoxControl..ctor(Int32 identifier) in c:\Dev\GuiTest\MultipleTextBoxControl.xaml.cs:line 31
at GuiTest.MainWindow25.CreateNewControl_Click(Object sender, RoutedEventArgs e) in c:\Dev\GuiTest\MainWindow25.xaml.cs:line 43
所以列表中最新的项目是MultipleTextBoxControl.xaml.cs第31行:
MainWindow25.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);
在这里放置一个断点并检查内容会发现MasterDataList为null,因为你没有在TextBoxControlList
初始化它:
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
所以这样做:
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
我个人强烈建议不要使用静态类成员,特别是在这种情况下,但无论哪种方式,这都是你问题的答案。
编辑:
我完全赞同AnjumSKhan正在制作的要点,尽管我个人会使用Inversion of Control(IoC)来解决这个问题。您真正想要做的是让子控件以主窗口代码稍后可以访问的方式注册其数据。正如AnjumSKhan指出的那样,孩子们不应该对他们的父母有任何了解,但是你也应该能够在你的子类中创建和单元测试这种行为,而无需创建父类。控制反转涉及将一个接口传递给它应该用来注册自己的子节点,一个非常简单的例子可能就是这样:
public interface IDataRegistrationService
{
void Register(MultipleTextBoxControl.TextBoxData data);
}
您的子窗口可以在其构造函数中接受对此类服务的引用,并按照以前的方式进行注册,但使用此服务:
public MultipleTextBoxControl(int identifier, IDataRegistrationService service)
{
InitializeComponent();
newTextBoxGroup.Identifier = identifier;
service.Register(newTextBoxGroup); // <---------
}
您的MainWindow类现在可以从此继承并在创建子项时传递对自身的引用(注意我还使MasterDataList成为MainWindow的常规属性):
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
public void Register(MultipleTextBoxControl.TextBoxData data)
{
MasterDataList.Add(data);
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID, this);
UserControlContainer.Children.Add(newUserControl);
}
通过这样做,您已经正式确定了父母和孩子之间的关系,并阻止了自己或他人在两者之间添加更多可能难以解开或稍后更改的关系。您现在也可以创建子项的实例并使用模拟对象(例如,使用Moq库)来测试它的行为是否符合预期。通过保持良好的关注点分离,您可以自由地传递任何您想要的服务......也许稍后您将决定每个面板需要多个面板和一个服务器。
IoC的一个缺点是,你最终会在整个项目中向服务提供者传递引用,中间层保持对层次结构中较高层对象的引用,其唯一目的是将它们传递到较低的位置。这就是依赖注入框架解决的问题(例如Ninject)。他们摆脱了所有参数传递,你的最终代码看起来像这样:
public partial class MultipleTextBoxControl : UserControl
{
// this gets created by the DI framework, with identifier set automatically
[Inject] private TextBoxData newTextBoxGroup { get; set; }
// this get injected automatically when the class is create
[Inject] private IDataRegistrationService DataService {get; set;}
public MultipleTextBoxControl()
{
InitializeComponent();
}
// this gets called immediately after the constructor
public void Initialize()
{
// and you do any custom initialization here, using injected components
this.DataService.Register(newTextBoxGroup);
}
答案 1 :(得分:1)
将TextBoxControlList类更改为:
public static class TextBoxControlList
{
public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
static TextBoxControlList() {
MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
}
}
更好的方法:
重构#1
MultipleTextBoxControl.xaml.cs
public partial class MultipleTextBoxControl : UserControl
{
TextBoxData _newTextBoxGroup;
public TextBoxData TextBoxGroup { get { return _newTextBoxGroup; } }
public MultipleTextBoxControl(int identifier)
{
InitializeComponent();
_newTextBoxGroup = new TextBoxData(identifier);
}
public class TextBoxData
{
public int Identifier { get; set; }
public string TextBox1Data { get; set; }
public string TextBox2Data { get; set; }
public TextBoxData(int identifier)
{
Identifier = identifier;
TextBox1Data = "Unchanged Textbox 1";
TextBox2Data = "Unchanged Textbox 2";
}
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
_newTextBoxGroup.TextBox1Data = textBox1.Text;
}
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
_newTextBoxGroup.TextBox2Data = textBox2.Text;
}
}
MainWindow.cs
public partial class MainWindow : Window
{
int MultipleTextBoxControlID = 1;
public static List<MultipleTextBoxControl.TextBoxData> MasterDataList;
static MainWindow() {
MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
}
public MainWindow()
{
InitializeComponent();
}
private void CreateNewControl_Click(object sender, RoutedEventArgs e)
{
MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
UserControlContainer.Children.Add(newUserControl);
MasterDataList.Add(newUserControl.TextBoxGroup);
}
private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
{
foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in MasterDataList)
{
List<string> userControlLine = new List<string>();
userControlLine.Add(textBoxPanel.Identifier.ToString());
userControlLine.Add(textBoxPanel.TextBox1Data);
userControlLine.Add(textBoxPanel.TextBox2Data);
MessageBox.Show(string.Join(",", userControlLine));
}
}
}
答案 2 :(得分:0)
我在用户控件中使用了对 MainWindow 的半全局引用,如下所示:
public partial class MainWindow : Window
{
public static MainWindow Me = null;
public MainWindow()
{
Me = this;
InitializeComponent();
然后我在用户控件中使用了这个“我”:
public partial class UserControlTable : UserControl
{
DataCenterSender m_DataCenterSender = new DataCenterSender(MainWindow.Me.Get_DataCenter());
并且在 XAML 编辑器中有一个空指针。
看起来像在 XAML 编辑器中没有调用 MainWindow 的构造函数,因为 MainWindow.Me==null 在这种情况下用户控制构造函数失败。