简体   繁体   中英

How do I properly bind a WPF TreeView control and effectively display a hierarchy of the data?

Ok, so I normally wait until the last possible moment to submit a question but I could really use some help.

I'm attempting to bind an ObservableCollection<Contact_Info> to a WPF TreeView Control. Contact_Info contains properties for contact information and implements INotifyPropertyChanged . It also contains a list of more detailed information for the given Contact .

I'm having trouble with 2 things. 1) I need to display on the TreeViewItem header the name of the Company however, a lot of the Contacts have a missing Company Name in which, I want to use the First and Last name. I have attempted to use things like FallBackValue and TargetNullValue but the CompanyName is not null, its just empty so I can't seem to get that right. Do I need a converter? 2) I cannot seem to get the hierarchy correct in that, I want all Contacts of the same AccountID to be grouped together within a TreeViewItem . Currently, I can only get the Company Name to display on the TreeViewItem and expanding it displays a blank value and there are still duplicate Company Names displayed..

Code for my attempt at a HierarchicalDataTemplate placed under the DockPanel.Resources (not sure if that's the right place either).

    <local:Main_Interface x:Key="MyList" />
       <HierarchicalDataTemplate DataType = "{x:Type 
                                 local:Contact_Info}" ItemsSource = "{Binding Path=OppDataList}">
            <TextBlock Text="{Binding Path=CompanyName}"/>
        </HierarchicalDataTemplate>
       <DataTemplate DataType = "{x:Type sharpspring:SharpspringOpportunityDataModel}" >
             <TextBlock Text="{Binding Path=ProjectName}"/>
        </DataTemplate>

The above code is the most recent thing I have tried among countless other ways. I feel that I'm really close but at this point I could really use some help from SO..

The other classes are a little long so let me know if you need more. Any help provided is greatly appreciated!

Edit 1: Contact_Info

public class Contact_Info : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    internal SharpspringLeadDataModel LeadDataModel;
    internal SharpspringOpportunityDataModel OppData;
    public List<SharpspringOpportunityDataModel> OppDataList { get; set; }// Should i make this ObservableCollection?

    public Project_Info ProjectInfo { get; set; }
    public bool IsDirty { get; set; }
    public bool IsNew { get; set; }

    #region Properties

    public long AccountID
    {
        get => LeadDataModel.AccountID;
        set
        {
            if (value != LeadDataModel.AccountID)
            {
                LeadDataModel.AccountID = value;
                NotifyPropertyChanged();
            }
        }
    }
    public long LeadID
    {
        get => LeadDataModel.LeadID;
        set
        {
            if (value != LeadDataModel.LeadID)
            {
                LeadDataModel.LeadID = value;
                NotifyPropertyChanged();
            }
        }
    }
    // Lead Info
    public string CompanyName
    {
        get => LeadDataModel.CompanyName;
        set
        {
            if (value != LeadDataModel.CompanyName)
            {
                LeadDataModel.FaxNumber = value;
                NotifyPropertyChanged();
            }
        }
    }

    public Contact_Info(SharpspringLeadDataModel lead, SharpspringOpportunityDataModel opp)
    {
        LeadDataModel = lead;
        OppData = opp;
        ProjectInfo = new Project_Info(this);

    }

    public SharpspringLeadDataModel GetDataModel()
    {
        return LeadDataModel;
    }

    public SharpspringOpportunityDataModel GetOppModel()
    {
        return OppData;
    }

    public Project_Info GetProjectInfoModel()
    {
        return ProjectInfo;
    }

    public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        IsDirty = true;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Edit 2: Currently populates tree but does not group Companies together...

private void PopulateTreeView(List<SharpspringLeadDataModel> lead_list, List<SharpspringOpportunityDataModel> opp_list)
    {
        Contacts = new ObservableCollection<Contact_Info>();
        var complete2 =
                        from lead in lead_list
                        let oppList = from o in opp_list
                                      where o.PrimaryLeadID == lead.LeadID
                                      select o
                        select new Contact_Info()
                        {
                            LeadDataModel = lead,
                            OppData = oppList.DefaultIfEmpty(new SharpspringOpportunityDataModel()).First(),
                            OppDataList = oppList.ToList(),
                        };

        Contacts = complete2.ToObservableCollection();
   Lead_Treeview.ItemsSource = Contacts;
}

I think your problem is really with using List instead of ObservableCollection for OppDataList . Using your code (reduced to minimal needed) but with the ObservableCollection driving the collection it works fine for me.

Note that my code will work fine with List even because its all static but in a proper app you'll need OC to track changes in collection.

Disclaimer: I'm using code behind to answer this question to keep it simple but I do not recommend any such use in any real app

MainWindow.xaml
 <Window x:Class="WpfApp1.MainWindow" 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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="240" Width="320"> <TreeView ItemsSource="{Binding Contacts}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Contact_Info}" ItemsSource="{Binding Path=OppDataList}"> <HierarchicalDataTemplate.ItemTemplate> <DataTemplate DataType="{x:Type local:SharpspringOpportunityDataModel}"> <TextBlock Text="{Binding Path=ProjectName}" /> </DataTemplate> </HierarchicalDataTemplate.ItemTemplate> <TextBlock Text="{Binding Path=CompanyName}" /> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Window> 
MainWindow.xaml.cs
 using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using WpfApp1.Annotations; namespace WpfApp1 { public partial class MainWindow : INotifyPropertyChanged { public MainWindow() { InitializeComponent(); DataContext = this; } public ObservableCollection<Contact_Info> Contacts { get; } = new ObservableCollection<Contact_Info> { new Contact_Info { CompanyName = "Apple", OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}} }, new Contact_Info { CompanyName = "Google", OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}} }, new Contact_Info { CompanyName = "Microsoft", OppDataList = {new SharpspringOpportunityDataModel {ProjectName = "World take over"}} } }; public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Contact_Info { public string CompanyName { get; set; } public ObservableCollection<SharpspringOpportunityDataModel> OppDataList { get; } = new ObservableCollection<SharpspringOpportunityDataModel>(); } public class SharpspringOpportunityDataModel { public string ProjectName { get; set; } } } 
Screenshot

截图

based on my understanding your OppDataList is the sub property for the given Contract info and you want it to be collapsible.

In such case you can do

    <ListBox ItemsSource="{Binding YourContactList}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Expander Header="{Binding ProjectName}">
                    <Expander Header="{Binding CompanyName}">
                        <ListBox ItemsSource="{Binding OppDataList}">
                            <!--Your OppDataList display-->
                        </ListBox>
                    </Expander>
                </Expander>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

End result will look similar to this

+Contract1
-Contract2
   -Project2
      -Company2
         -Opp
            1
            2
            3
+Contract3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM