0

I have an XML file that I want to use to populate a model. 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 the selected study.

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>

Model.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; }
        }

    }
}

The 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>

I know my ViewModel is not correct, that is mainly where my problems are. Could anyone please show me how I can use this XML file along with the Model to populate my ListBox with the studyTitle's in my View. I want to be able to add a new study to the XML file and have it be included in the listbox in the View.

1 Answers1

1

Since the name of your model class Studies is somehow irritating (as it maps to a Study XML object) I renamed it to Study.

I also recommend to implement the INotifyDataErrorInfo instead (example). It replaces the old an obsolete 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>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • I am getting a NullReferenceException Error seems that System.Xml.Linq.XContainer.Element returns null. The exception occurs in the ReadXml() method. I created a constructor for the ViewModel class and called the ReadXml() method within the constructor. Could you also explain the relationship between the ```public ObservableCollection Studies { get; set; }``` and the ```List studies = xmlRootElement.Decendants() .Select(CreateModelFromStudyXmlNode) .ToList();``` the MainWindow.xaml is using the Observable Collection right? – millenniumThalken Jan 31 '20 at 19:40
  • I was able to fix the NullReferenceException by adding "Study" as an argument to Descendants ```List studies = xmlRootElement.Descendants("Study") .Select(CreateModelFromStudyXmlNode) .ToList();``` Although when I run the app nothing appears in the TextBox, Do I need to assign the List studies to the ObservableCollection? The xaml is pulling from the ObservableCollection right? – millenniumThalken Jan 31 '20 at 20:27
  • Yes, you have to sign the result of the LINQ query to the `Studies` collection: `this.Studies = new ObservableCollection(studies);`. I updated the answer. – BionicCode Feb 01 '20 at 00:25
  • I had just changed the ObservableCollection to a List since I do not for see having to update my StudyTitle ever, but switched it back to the ObservableCollection upon your updated suggestion. Everything is working as expected now, thank you. – millenniumThalken Feb 03 '20 at 15:24