Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse xml into ObservableCollection using Linq. Data bind to datagrid using wpf c#

I'm new to linq, wpf and C#. I've managed to research my way to a functional component. I have attacked data binding successfully but I'm struggling with performance. I'm reading in a static external xml file (i.e. database) and want to display it to users using a wpf datagrid. The additional bit of info is that I'm using a user-controlled wpf combobox to filter how much data from the database is shown in the grid. I'd like to use linq to accomplish this task but I can't seem to get it to perform correctly.

C# file:

namespace ReadPipeXMLDB

{
public partial class ReadDB : Window, INotifyPropertyChanged { private XDocument xmlDoc = null;
const string ALL = "All";

    // Constructor
    public ReadDB()
    {
        InitializeComponent();

        // Load xml            
        xmlDoc = XDocument.Load("DataBase.xml");
        this.DataContext = this;                    
    }

    private ObservableCollection<CPipeData> _col;
    public ObservableCollection<CPipeData> Col
    {
        get { return _col; }
        set
        {
            if (_col == value)
                return;

            _col = value;
            OnPropertyChanged(() => Col);
        }
    }

    private ObservableCollection<CMfgData> _mfgCollection;
    public ObservableCollection<CMfgData> MfgCollection
    {
        get { return _mfgCollection; }
        set
        {
            if (_mfgCollection == value)
                return;

            _mfgCollection = value;
            OnPropertyChanged(() => MfgCollection);
        }        
    }

    private ObservableCollection<string> _mfgNames;
    public ObservableCollection<string> MfgNames
    {
        get { return this._mfgNames; }
        set
        {
            if (this._mfgNames == value)
                return;

            this._mfgNames = value;
            OnPropertyChanged(() => MfgNames);
        }
    }



    #region Notify Event Declaration and Definition

    public event PropertyChangedEventHandler PropertyChanged;

    public virtual void OnPropertyChanged<T>(Expression<Func<T>> property)
    {
        PropertyChangedEventHandler eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            var memberExpression = property.Body as MemberExpression;
            eventHandler(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
        }
    }

    #endregion


    public class CMfgData : ReadDB
    {
        private string _mfgName;
        //private ObservableCollection<CPipeData> _pipeDataCollection;



        /*public CMfgData(string mfgName, CPipeData pipeData)
        {               
            _mfgName = mfgName;
            _pipeData = pipeData;
        }*/

        #region CMfgData Property Definitions

        public string MfgName
        {
            get { return _mfgName; }
            set
            {
                if (_mfgName == value)
                    return;

                _mfgName = value;
                OnPropertyChanged(() => MfgName);
            }
        }

       /* public ObservableCollection<CPipeData> PipeDataCollection
        {
            get { return _pipeDataCollection; }
            set
            {
                if (_pipeDataCollection == value)
                    return;

                _pipeDataCollection = value;
                OnPropertyChanged(() => PipeDataCollection);
            }
        }*/

        #endregion
    }

    public class CPipeData : ReadDB
    {
        // PipeData Property Declarations
        private string _nominal;
        private string _sched;
        private string _id;
        private string _od;
        private string _wt;

        public CPipeData()
        {
            _nominal = "";
            _sched = "";
            _id = "";
            _od = "";
            _wt = "";
        }

        // Constructor
        public CPipeData(string nominal, string sched, string id, string od, string wt)
        {
            _nominal = nominal;
            _sched = sched;
            _id = id;
            _od = od;
            _wt = wt;
        }

        #region CPipeData Property Definitions

        public string Nominal
        {
            get { return _nominal; }
            set
            {
                if (_nominal == value)
                    return;

                _nominal = value;
                OnPropertyChanged(() => Nominal);
            }
        }

        public string Sched
        {
            get { return _sched; }
            set
            {
                if (_sched == value)
                    return;

                _sched = value;
                OnPropertyChanged(() => Sched);
            }
        }

        public string ID
        {
            get { return _id; }
            set
            {
                if (_id == value)
                    return;

                _id = value;
                OnPropertyChanged(() => ID);
            }
        }

        public string OD
        {
            get { return _od; }
            set
            {
                if (_od == value)
                    return;

                _od = value;
                OnPropertyChanged(() => OD);
            }
        }

        public string WT
        {
            get { return _wt; }
            set
            {
                if (_wt == value)
                    return;

                _wt = value;
                OnPropertyChanged(() => WT);
            }
        }

        #endregion  
    }



    private void mfgrComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // Update database grid 
        if (mfgrComboBox1.SelectedValue is string)
        {                
            PopulateGrid(mfgrComboBox1.SelectedValue as string);
        }
    }


    private void Window_Loaded(object sender, RoutedEventArgs e)
    {                    
        /*MfgCollection = new ObservableCollection<CMfgData>(                                
            from mfg in xmlDoc.Root.Elements("Mfg")                               
            //where mfg.Attribute("name").Value == comboValue
            select new CMfgData
            {
                MfgName = mfg.Attribute("name").Value,
                PipeDataCollection = 
                    new ObservableCollection<CPipeData>
                    (from pipe in mfg.Elements("pipe")                                
                    select new CPipeData
                    {    
                        Nominal = pipe.Element("Nominal").Value, 
                        Sched = pipe.Element("Schedule").Value, 
                        ID = pipe.Element("ID").Value, 
                        OD = pipe.Element("OD").Value, 
                        WT = pipe.Element("Wall_Thickness").Value
                    })

            });*/
    }


    private void mfgrComboBox1_Loaded(object sender, RoutedEventArgs e)
    {
        // Make sure xml document has been loaded            
        if (xmlDoc != null)
        {                              
            ObservableCollection<string> tempCollection =  new ObservableCollection<string>(                
                from n in xmlDoc.Root.Elements("Mfg").Attributes("name")
                select n.Value);                

            // Add the additional "All" filter
            tempCollection.Insert(0, ALL);

            // Assign list to member property. This is done last so the property event gets fired only once                
            MfgNames = tempCollection;                

            PopulateGrid(ALL);                
        }
    }


    private void PopulateGrid(string comboValue)
    {
        if (mfgrComboBox1.Items.IndexOf(comboValue) > -1)
        {                    
            Col = new ObservableCollection<CPipeData>(
                from mfg in xmlDoc.Root.Elements("Mfg")
                where mfg.Attribute("name").Value == comboValue
                from pipe in mfg.Elements("pipe")
                select new CPipeData
                {
                    Nominal = pipe.Element("Nominal").Value,
                    Sched = pipe.Element("Schedule").Value,
                    ID = pipe.Element("ID").Value,
                    OD = pipe.Element("OD").Value,
                    WT = pipe.Element("Wall_Thickness").Value
                });
        }
    }
}

}

Xaml:

<Window x:Class="ReadPipeXMLDB.ReadDB"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
    Title="Standard Pipe Sizes" Height="849" Width="949" Loaded="Window_Loaded">

<Grid>        

    <!-- Manufactuer filter combobox -->
    <ComboBox Name="mfgrComboBox1"
              ItemsSource="{Binding Path=MfgNames}"  
              SelectedIndex="0"
              Height="23" Width="286"
              HorizontalAlignment="Left" VerticalAlignment="Top"                  
              Margin="20,20,0,0" SelectionChanged="mfgrComboBox1_SelectionChanged" Loaded="mfgrComboBox1_Loaded" />     

    <!-- Units combobox -->
    <ComboBox Height="23" HorizontalAlignment="Left" Margin="320,20,0,0" Name="dimensionsComboBox2" VerticalAlignment="Top" Width="87" />              

    <!-- Pipe database display grid -->
    <DataGrid Name="dataGrid1" IsReadOnly="True" AutoGenerateColumns="False" Margin="20,60,20,20" ItemsSource="{Binding Col}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Nominal"  Binding="{Binding Path=Nominal}"/>
            <DataGridTextColumn Header="Schedule" Binding="{Binding Path=Sched}"/>
            <DataGridTextColumn Header="ID" Binding="{Binding Path=ID}"/>
            <DataGridTextColumn Header="OD" Binding="{Binding Path=OD}"/>
            <DataGridTextColumn Header="Wall Thickness" Binding="{Binding Path=WT}" Width="*"/>
        </DataGrid.Columns>                       
    </DataGrid>        
</Grid>

XML:

<DBRoot>
   <Mfg name="A Manufac">
    <pipe>
      <Nominal>testdata</Nominal>
      <Schedule>testdata</Schedule>
      <OD>testdata</OD>
      <Wall_Thickness>testdata</Wall_Thickness>
      <ID>testdata</ID>
    </pipe>
    <pipe>
      <Nominal>testdata</Nominal>
      <Schedule>testdata</Schedule>
      <OD>testdata</OD>
      <Wall_Thickness>testdata</Wall_Thickness>
      <ID>testdata</ID>
    </pipe>
  </Mfg>
  <Mfg name="B Manufac">
    <pipe>
      <Nominal>testdata</Nominal>
      <Schedule>testdata</Schedule>
      <OD>testdata</OD>
      <Wall_Thickness>testdata</Wall_Thickness>
      <ID>testdata</ID>
    </pipe>
    <pipe>
      <Nominal>testdata</Nominal>
      <Schedule>testdata</Schedule>
      <OD>testdata</OD>
      <Wall_Thickness>testdata</Wall_Thickness>
      <ID>testdata</ID>
    </pipe>
    </Mfg>
  </DBRoot>

The PopulateGrid call is slow because I create a new ObservableCollection every time the combobox changes value. I'm not use to working with collections and linq so if someone can offer my a more robust alternative I'd appreciate it!

like image 433
neo Avatar asked Apr 21 '26 20:04

neo


2 Answers

You can save yourself some trouble by binding directly to the XML file:

XAML

<Grid>
    <Grid.DataContext>
        <XmlDataProvider Source="DataBase.xml"/>
    </Grid.DataContext>

    <StackPanel>
        <ComboBox ItemsSource="{Binding XPath=/DBRoot/Mfg}" Name="comboBox" SelectedIndex="0">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding XPath=@name}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <DataGrid ItemsSource="{Binding ElementName=comboBox, Path=SelectedItem}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding XPath=Nominal}" Header="Nominal"/>
                <DataGridTextColumn Binding="{Binding XPath=Schedule}" Header="Schedule"/>
                <DataGridTextColumn Binding="{Binding XPath=OD}" Header="OD"/>
                <DataGridTextColumn Binding="{Binding XPath=Wall_Thickness}" Header="Wall Thickness"/>
                <DataGridTextColumn Binding="{Binding XPath=ID}" Header="ID"/>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Grid>
like image 68
Dan Busha Avatar answered Apr 23 '26 10:04

Dan Busha


Oh my goodness. Your problem starts with ObservableCollection<string> MfgNames. String has no intelligence and you are building data from scratch every time. Use a class.

public class Mfg
{
    public string Name { get; private set; }

    public ObservableCollection <CPipeData> pipes { ....

Then in the detail you just bind

ItemsSounce="{binding ElementName=cbMfg Path=SelectedItem.Pipes}"

Look up Master Detail on MSDN.Microsoft.Com

If several Mfg use the same pipe then you would create a hashset with the relationship and pass the hashset to Mfg and use LINQ the filter from that single hashset. Override GetHash.

like image 41
paparazzo Avatar answered Apr 23 '26 09:04

paparazzo