ViewModel 中的 XML 到 LINQ 以填充模型
Posted
技术标签:
【中文标题】ViewModel 中的 XML 到 LINQ 以填充模型【英文标题】:XML to LINQ in ViewModel to populate Model 【发布时间】:2020-01-29 21:04:25 【问题描述】:我有一个要用于填充模型的 XML 文件。 I have multiple studies within my XML and my goal is to populate a list with all the study names and when the study name is selected and submit button is pressed then the application will navigate to a new window but carry the rest of the XML data from选定的研究。
XML
<?xml version="1.0" encoding="utf-8" ?>
<Studies>
<Study>
<StudyTitle>SPIROMICS2</StudyTitle>
<ImportDirectory>Z:\SPIROMICS\Human_Scans\Dispatch_Received\NO_BACKUP_DONE_HERE\IMPORT</ImportDirectory>
</Study>
<Study>
<StudyTitle>BLF</StudyTitle>
<ImportDirectory>Z:\BLF\Human Scans\Dispatch Received\IMPORT</ImportDirectory>
</Study>
</Studies>
模型.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DICOM_Importer.Models
/// <summary>
/// Model for all active studies within the APPIL lab
/// </summary>
public class Studies : INotifyPropertyChanged, IDataErrorInfo
private string studyTitle;
private string importDirectory;
public string StudyTitle
get return studyTitle;
set
studyTitle = value;
OnPropertyChanged("StudyTitle");
public string ImportDirectory
get return importDirectory;
set
importDirectory = value;
OnPropertyChanged("ImportDirectory");
#region PropertyChangedEventHandler
//Create a PropertyChangedEventHandler variable that you can use to handle data changing
public event PropertyChangedEventHandler PropertyChanged;
//create the method that will handle the updating of data if a property is changed.
private void OnPropertyChanged(string propertyChanged)
//bring in the event handler variable that you created and assign it to this methods 'handler' variable
PropertyChangedEventHandler handler = PropertyChanged;
//if the the handler is not null, which means the property has been changed, then hand in the new value to the handler to be changed
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyChanged));
#endregion
#region DataErrorInfo
/// <summary>
/// Getter and Setter for the Error message that we setup through the IDataErrorInfo interface
/// </summary>
public string Error
get;
set;
//the column name passed in will be a property on the Studies Object that we want to validate
//this validatation is looking at the StudyTitle property. If the StudyTitle property is 'null' or just white space then we are going to add the error message
//"Study Title cannot be null or empty" Otherwise if StudyTitle is fine the error message will be 'null'
public string this[string columnName]
get
if (columnName == "StudyTitle")
if (String.IsNullOrWhiteSpace(StudyTitle))
Error = "Study Title cannot be null or empty";
else
Error = null;
return Error;
#endregion
ViewModel.cs
using DICOM_Importer.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace DICOM_Importer.ViewModels
internal class HomeViewModel
//setting up some variable that will be use through this class
// the private read only is setting up a Studies data type that will have a studies object attached to it
private readonly Studies studies;
public string studyTitle;
/// <summary>
/// Initializes a new instance of the CustomerViewModel class
/// </summary>
public HomeViewModel()
string path_to_debug_folder = Directory.GetCurrentDirectory();
string path = Path.GetFullPath(Path.Combine(path_to_debug_folder, @"..\..\")) + @"Data\Studies.xml";
//the 'path' variable is the path to the XML file containing all Studies data, we first just check the file does exist, we then create
//an instance of the Serializer class and use that class on the XML file using the Deserialize method from the class. Then attached the data to an
//instance of a Studies object that we created a 'private readonly' variable for.
if (File.Exists(path))
XElement xe = XElement.Load(path);
var x = xe.Elements();
foreach (var tag in x)
studyTitle = tag.FirstNode.ToString();
/// <summary>
/// creates an instance of a Customer
/// </summary>
public Studies Studies
get return studies;
View.xaml
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:DICOM_Importer.Views"
mc:Ignorable="d"
Background="Gold"
Title="DICOM Importer" Height="385" Width="600">
<Grid Style="StaticResource gridBackground">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Column="1" Grid.Row="0" Style="StaticResource homeTitle">DICOM IMPORTER</Label>
<Image x:Name="appil_logo" Grid.Column="0" Grid.Row="0" Grid.RowSpan="4" Source="/assets/appil_logo.png"/>
<Border Grid.Column="1" Grid.Row="0" Style="StaticResource studyListHeader" Width="Auto">
<Label Style="StaticResource studyListText">Active Studies</Label>
</Border>
<ListBox Name="activeStudiesListBox" Grid.Column="1" Grid.Row="2" >
<Label Content="Binding Studies.StudyTitle" />
</ListBox>
<Button Grid.Row="3" Grid.Column="1" Style="StaticResource buttonStyle">Select</Button>
</Grid>
</Window>
我知道我的 ViewModel 不正确,这主要是我的问题所在。谁能告诉我如何使用这个 XML 文件和模型来在我的视图中使用 studyTitle 填充我的列表框。我希望能够向 XML 文件添加一项新研究,并将其包含在视图的列表框中。
【问题讨论】:
【参考方案1】:由于您的模型类的名称 Studies
有点烦人(因为它映射到 Study
XML 对象)我将其重命名为 Study
。
我还建议改用INotifyDataErrorInfo
(example)。它取代了旧的过时的IDataErrorInfo
。
ViewModel.cs
// If proeprty values are expected to change
// and this changes are required to propagate to the view
// this class must implement INotifyPropertyChanged
public class ViewModel
private void ReadXml(string filePath)
var xmlRootElement = XElement.Load(path);
List<Studies> studies = xmlRootElement.Decendants("Study")
.Select(CreateModelFromStudyXmlNode)
.ToList();
this.Studies = new ObservableCollection<Study>(studies);
private Study CreateModelFromStudyXmlNode(XElement studyXmlNode)
var newStudy = new Study();
newStudy.StudyTitle = studyXmlNode.Element("StudyTitle").Value;
newStudy.ImportDirectory = studyXmlNode.Element("ImportDirectory").Value;
return newStudy;
public ObservableCollection<Study> Studies get; set;
Study.cs
public class Study
public string StudyTitle get; set;
public string ImportDirectory get; set;
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListBox ItemsSource="Binding Studies" >
<ListBox.ItemTemplate>
<DataTemplate DataType="x:Type Study">
<TextBlock Text="Binding StudyTitle" />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
</Window>
【讨论】:
我收到 NullReferenceException 错误似乎 System.Xml.Linq.XContainer.Element 返回 null。异常发生在 ReadXml() 方法中。我为 ViewModel 类创建了一个构造函数,并在构造函数中调用了 ReadXml() 方法。能否解释一下public ObservableCollection<Study> Studies get; set;
和 List<Studies> studies = xmlRootElement.Decendants() .Select(CreateModelFromStudyXmlNode) .ToList();
MainWindow.xaml 使用 Observable Collection 之间的关系对吗?
我能够通过将“Study”作为参数添加到 Descendants List<Studies> studies = xmlRootElement.Descendants("Study") .Select(CreateModelFromStudyXmlNode) .ToList();
来修复 NullReferenceException 虽然当我运行应用程序时 TextBox 中没有出现任何内容,但我需要分配 ListStudies
集合:this.Studies = new ObservableCollection<Study>(studies);
。我更新了答案。
我刚刚将 ObservableCollection 更改为列表,因为我认为不必更新我的 StudyTitle,但根据您更新的建议将其切换回 ObservableCollection。现在一切都按预期工作,谢谢。以上是关于ViewModel 中的 XML 到 LINQ 以填充模型的主要内容,如果未能解决你的问题,请参考以下文章
无法使用数据绑定Android与ViewModel中的XML通信
如何/我可以使用 linq to xml 以合理的内存消耗查询巨大的 xml 文件?