如何使用Linq Group By和Distinct for Treeview

时间:2014-11-12 05:19:30

标签: c# wpf linq treeview

我正在从XML文件中读取我的初始数据并将其返回为:

 List<ReportItem> ReportMapItems = Database.ReadXMLReportMap();

然后将ReportMapItems读入ReportTree,作为树视图的基本集合:

ReportTree = new ObservableCollection<ReportViewModel>(
            (from report in ReportMapItems
             select new ReportViewModel(report, ReportMapItems))
            .ToList());

XAML

<TreeView ItemsSource="{Binding ReportTree}"  >                  
            <TreeView.Resources>
                                    <HierarchicalDataTemplate 
                DataType="{x:Type r:ReportViewModel}" 
                ItemsSource="{Binding Children}"
                >
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding ReportName}" Width="150" />
                        <TextBlock Text="{Binding Comment}" />
                    </StackPanel>
                </HierarchicalDataTemplate>

                <HierarchicalDataTemplate 
                DataType="{x:Type r:NetworkViewModel}" 
                ItemsSource="{Binding Children}"
                >
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding NetworkIP}" Width="110" />                       
                        <TextBlock Text="{Binding NetworkName}" />
                    </StackPanel>
                </HierarchicalDataTemplate>


                <DataTemplate DataType="{x:Type r:PrinterViewModel}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding IPAddress}"   Width="100" />
                        <TextBlock Text="{Binding PrinterFullName}" Width="300" />
                         <TextBlock Text="{Binding Location}" />
                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>

问题是树视图正在为ReportMapItems中的每个报表条目创建一个节点,因此多次显示相同的报表。

我需要的是为每个不同的ReportName生成一个报告节点,并且在此节点下为每个不同的NetWorkIP生成一个唯一的网络节点。最后,在其相应的NetWorkIP和ReportName下为每个PrinterFullName生成ONLY ONE打印机节点。

如何使用LINQ来规范化ReportMapItems或ReportTree,以便TreeView正确显示信息(以标准化方式)????

提前感谢您的帮助。

编辑:根据请求,以下是一些类的定义。 (这不是一个代码转储,但遗憾的是涉及到很多类。如果需要更多信息,我很乐意添加它。)

public class ReportViewModel : TreeViewModelBase
{
    private string reportname;
    readonly IList<ReportItem> reportitems;
    private ReportItem report;
    private IList<ReportItem> ReportMapItems;
    private ReportItem reportitem;

    // Each ReportViewModel is a level 1 node in the Tree. Each ReportViewModel should only have reportitems specific to the
    // report being modeled.
    public ReportViewModel(ReportItem reportitem, IList<ReportItem> reportitems)
        : base(null, true)
    {
        this.reportitem = reportitem;
        this.reportitems = reportitems;
    }

    public ReportViewModel(Report SelectedReport, UI.Network.PRINTERMAP SelectedPrinter)
        : base(null, true)
    {
        ReportItem r = new ReportItem { 
            ReportName = SelectedReport.ReportName,
            Comment = SelectedReport.Comment,
            IPAddress = SelectedPrinter.IPAddress, 
            PrinterDescription = SelectedPrinter.Description, 
            PrinterFullName = SelectedPrinter.PrinterFullName,
            Location = SelectedPrinter.Location,
            NetworkIP = SelectedPrinter.NetworkIP,
            NetworkName = SelectedPrinter.NetworkName
        };

        this.reportitem = r;
        this.reportitems = new List<ReportItem>();
        this.reportitems.Add(r);
    }

    public string ReportName
    {
        get { return reportitem.ReportName; }
    }

    public string Comment
    {
        get { return reportitem.Comment; }
    }

    public IList<ReportItem> ReportItems
    {
        get { return reportitems; }
    }

    // LoadChildren() is called only when the ReportViewModel is expanded by user clicking on '+'.
    protected override void LoadChildren()
    {
        foreach (Network network in Database.GetNetwork(ReportName, ReportItems))
            base.Children.Add(new NetworkViewModel(network, this));
    }
}

   public class NetworkViewModel : TreeViewModelBase
{
    readonly Network _network;
    readonly ReportViewModel _reportviewmodel;

    public NetworkViewModel(Network network, ReportViewModel parentRegion)
        : base(parentRegion, true)
    {
        _network = network;
        _reportviewmodel = parentRegion;
    }

    public string NetworkIP
    {
        get { return _network.NetworkIP; }
    }

    public string NetworkName
    {
        get { return _network.NetworkName; }
    }

    public string ReportName
    {
        get { return _reportviewmodel.ReportName; }
    }

    public IList<ReportItem> ReportItems
    {
        get { return _reportviewmodel.ReportItems; }
    }

    // LoadChildren() is called only when the NetworkViewModel is expanded by user clicking on '+'.
    protected override void LoadChildren()
    {
        foreach (Printer printer in Database.GetPrinters(ReportName, NetworkIP, ReportItems))
            base.Children.Add(new PrinterViewModel(printer, this));
    }
}

public List<ReportItem> ReportMapItems { get; set; }

 public class ReportItem
{
    public string ReportName { get; set; }

    // Report description
    public string Comment { get; set; }

    // Printer IPAddress
    public string IPAddress { get; set; }

    // The PhysicalAddress of a device is its MAC.
    public string PhysicalAddress { get; set; }

    // Printer description
    public string PrinterDescription { get; set; }

    // Full Name of the printing queue
    public string PrinterFullName { get; set; }

    // Printer location
    public string Location { get; set; }

    // Network IP
    public string NetworkIP { get; set; }

    // Network Name
    public string NetworkName { get; set; }
}

   public class ReportTree
  {
    public ReportTree(string reportName)
    {
        this.ReportName = reportName;
    }

    public ReportTree()
    {
        // TODO: Complete member initialization
    }

    public string ReportName { get; set; }

    readonly List<Network> _networks = new List<Network>();
    public List<Network> Networks
    {
        get { return _networks; }
    }
}


 public struct NETWORK
{
    public string NetworkName { get; set; }
    public string NetworkIP { get; set; }
}

public struct PRINTERMAP
{
    // the PrinterName is the name from the printer que
    public string PrinterFullName { get; set; }

    public string MAC { get; set; }

    public string IPAddress { get; set; }

    public string Comment { get; set; }

    public string Description { get; set; }

    // Location is from the Device table, (not the printer que).
    public string Location { get; set; }

    public int Type { get; set; }

    public string Company { get; set; }

    public string Model { get; set; }

    // the DeviceName is the name from the NetBios.
    public string DeviceName { get; set; }

    public string Office { get; set; }

    public string NetworkIP { get; set; }

    public string NetworkName { get; set; }
}

1 个答案:

答案 0 :(得分:1)

这是我认为System.Linq球队错失的一种情况。但是,由于我们能够创建Extension methods,我们可以创建自己的DistinctBy<TSource, Tkey> IEnumerable扩展方法:

public static IEnumerable<TSource> DistinctBy<TSource, TKey> (
    this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> keys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (keys.Add(keySelector(element))) yield return element;
    }
}

这是一种非常简单的方法,只使用HashSet来“过滤”重复值,只返回不同的属性值。您可以像这样使用它:

IEnumerable<YourDataType> distinctCollection = fullCollection.
    DistinctBy<YourDataType, YourPropertyDataType>(d => d.PropertyToMakeDistinctBy);