【问题标题】:Parse xml into ObservableCollection using Linq. Data bind to datagrid using wpf c#使用 Linq 将 xml 解析为 ObservableCollection。使用 wpf c# 将数据绑定到数据网格
【发布时间】:2012-04-24 07:02:24
【问题描述】:

我是 linq、wpf 和 C# 的新手。我设法研究了实现功能组件的方法。我已经成功地攻击了数据绑定,但我在性能上苦苦挣扎。我正在读取一个静态外部 xml 文件(即数据库),并希望使用 wpf 数据网格将其显示给用户。额外的信息是我正在使用用户控制的 wpf 组合框来过滤网格中显示了多少来自数据库的数据。我想使用 linq 来完成这项任务,但我似乎无法让它正确执行。

C# 文件:

namespace ReadPipeXMLDB

{
公共部分类 ReadDB:窗口,INotifyPropertyChanged { 私有 XDocument xmlDoc = null;
常量字符串 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>

PopulateGrid 调用很慢,因为每次组合框更改值时我都会创建一个新的 ObservableCollection。我不习惯使用集合和 linq,所以如果有人可以为我提供更强大的替代方案,我将不胜感激!

【问题讨论】:

    标签: c# wpf linq data-binding


    【解决方案1】:

    您可以通过直接绑定到 XML 文件来省去一些麻烦:

    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>
    

    【讨论】:

    • 我是这样做的,但因为我不确定这样做是否“更好”而改变了。现在我看了一下,直接绑定到静态文件更有意义。谢谢!
    • 对此有一个后续问题。我想在 XML 文件中 not 的组合列表中添加一个附加项。 “全部”是项目,它会显示所有制造商和管道数据。我应该创建一个属性来完成这个吗?
    • Afaik 您无法将值注入绑定,因此您需要将 Combobox 源移动到视图模型。这会使事情变得更加复杂,您可能希望使用更像 Blam 的解决方案。
    【解决方案2】:

    哦,天哪。您的问题从 ObservableCollection MfgNames 开始。 String 没有智能,您每次都在从头开始构建数据。使用一个类。

    public class Mfg
    {
        public string Name { get; private set; }
    
        public ObservableCollection <CPipeData> pipes { ....
    

    然后在细节中你只是绑定

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

    在 MSDN.Microsoft.Com 上查找 Master Detail

    如果多个 Mfg 使用相同的管道,那么您将创建一个具有关系的哈希集并将该哈希集传递给 Mfg 并使用来自该单个哈希集的 LINQ 过滤器。覆盖 GetHash。

    【讨论】:

    • 如果您不介意,我想了解更多。我何时以及如何构建。每个制造商都有一套独特的管道,但用户也可以选择查看“所有”制造商。我已经在我的“Window_Loaded”函数中完成并注释掉了这段代码。基本上我正在构建 ObservableCollection 的 ObservableCollection。我发现这虽然只发生一次,但仍然很慢。我想知道如何以正确的方式执行 linq,并且在您看来,是直接绑定到 xml,就像在另一篇文章中显示的那样,更好吗?
    • 拥有一个为 cbMfg 绑定的公共列表。如果您没有多对多,则无需参与 LINQ。如果您获得所需的性能,请使用 Dan 的答案 - 我给了它 +1。
    • 它不会让我这样做,因为我没有 15 的声望。我刚刚创建了一个帐户。对不起。
    猜你喜欢
    • 2023-03-07
    • 1970-01-01
    • 2016-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-31
    • 2017-05-14
    • 2013-01-30
    相关资源
    最近更新 更多