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&lt;Study&gt; Studies get; set; List&lt;Studies&gt; studies = xmlRootElement.Decendants() .Select(CreateModelFromStudyXmlNode) .ToList(); MainWindow.xaml 使用 Observable Collection 之间的关系对吗? 我能够通过将“Study”作为参数添加到 Descendants List&lt;Studies&gt; studies = xmlRootElement.Descendants("Study") .Select(CreateModelFromStudyXmlNode) .ToList(); 来修复 NullReferenceException 虽然当我运行应用程序时 TextBox 中没有出现任何内容,但我需要分配 List研究 ObservableCollection? xaml 是从 ObservableCollection 中提取的,对吗? 是的,您必须将 LINQ 查询的结果签名到 Studies 集合:this.Studies = new ObservableCollection&lt;Study&gt;(studies);。我更新了答案。 我刚刚将 ObservableCollection 更改为列表,因为我认为不必更新我的 StudyTitle,但根据您更新的建议将其切换回 ObservableCollection。现在一切都按预期工作,谢谢。

以上是关于ViewModel 中的 XML 到 LINQ 以填充模型的主要内容,如果未能解决你的问题,请参考以下文章

LINQ to XML - 从文件加载 XML 片段

无法使用数据绑定Android与ViewModel中的XML通信

如何/我可以使用 linq to xml 以合理的内存消耗查询巨大的 xml 文件?

如何使用 List<object> 将对象值添加到 viewmodel

减少用于过滤的 linq 查询

在按属性选择时,后代中的XML到Linq元素会出现对象引用错误