JianYA
JianYA

Reputation: 3024

WPF Treeview adding sub items from database

I have a function that is supposed to return a list of repositories including their subs called LoadAllRepositories.

This is the typical output from it:

{
    "id": "1",
    "parent": "#",
    "text": "ACC",
    "TenantId": "11ff3c48-2b82-41a2-b4f2-ca914e2701c0",
    "ApplicationId": "793b0f48-5fea-4e89-8650-602a43099342",
    "Path": "DIRONE/ACC",
    "LinkPath": null,
    "Applications": null,
    "CreatedOn": "2020-09-25T16:25:59.196927"
},
{
    "id": "2",
    "parent": "1",
    "text": "2020",
    "TenantId": "11ff3c48-2b82-41a2-b4f2-ca914e2701c0",
    "ApplicationId": "793b0f48-5fea-4e89-8650-602a43099342",
    "Path": "DIRONE/ACC/2020",
    "LinkPath": null,
    "Applications": null,
    "CreatedOn": "2020-09-25T16:26:17.893116"
},
{
    "id": "3",
    "parent": "#",
    "text": "BCC",
    "TenantId": "11ff3c48-2b82-41a2-b4f2-ca914e2701c0",
    "ApplicationId": "793b0f48-5fea-4e89-8650-602a43099342",
    "Path": "DIRONE/BCC",
    "LinkPath": null,
    "Applications": null,
    "CreatedOn": "2020-09-25T16:25:59.196927"
},
{
    "id": "4",
    "parent": "3",
    "text": "2020",
    "TenantId": "11ff3c48-2b82-41a2-b4f2-ca914e2701c0",
    "ApplicationId": "793b0f48-5fea-4e89-8650-602a43099342",
    "Path": "DIRONE/BCC/2020",
    "LinkPath": null,
    "Applications": null,
    "CreatedOn": "2020-09-25T16:25:59.196927"
}

if the parent is #, that means it is a root item.

I am trying to dynamically populate my treeview in wpf using this data schema but so far I have been unable to properly do it. My current code returns a stack overflow error due to it looping to infinity. I am just stuck on how to solve this issue and how to display it on the view.

Here is the function:

private List<DocumentRepository> LoadAllRepositories(string ApplicationId, string TenantId)
{
    List<DocumentRepository> data = new List<DocumentRepository>();
    List<DocumentRepository> initialData = new List<DocumentRepository>();
    var gtl = GetAllRepositories(ApplicationId, TenantId);
    if (gtl == null)
    {
        Error("Unable to establish a connection to the Server");
        System.Windows.Application.Current.Shutdown();
        return null;
    }
    foreach (var item in gtl)
    {
        initialData.Add(new DocumentRepository
        {
            Id = item.Id,
            parent = item.parent,
            TenantId = item.TenantId,
            ApplicationId = item.ApplicationId,
            text = item.text,
            Path = item.Path,
            LinkPath = item.LinkPath
        });
    };
    var c = GetChildren(gtl, null, TenantId);
    return data;
}

private List<DocumentRepository> GetChildren(List<DocumentRepository> Input, string ParentId, string TenantId)
{
    if (ParentId == null)
    {
        ParentId = "#";
    }
    List<DocumentRepository> data = new List<DocumentRepository>();
    foreach (DocumentRepository child in Input.Where(x => x.parent == ParentId && x.TenantId == TenantId))
    {
        child.Children = LoadAllRepositories(ApplicationId, TenantId);
        data.Add(child);
    }
    return data;
}

My View:

<TreeView Name="DocumentRepositoryTreeview">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding lar}" DataType="{x:Type model:DocumentRepository}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding text}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    var lar = LoadAllRepositories(ApplicationId, TenantId);
    var dis = new ObservableCollection<DocumentRepository>(lar);
    this.DataContext = dis;
    DocumentRepositoryTreeview.ItemsSource = lar;
}

Upvotes: 1

Views: 366

Answers (2)

aepot
aepot

Reputation: 4824

Here's the example. I stored the json into the file "data.json"

INPC Implementation

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Data

public class DocumentRepository : NotifyPropertyChanged
{
    private ObservableCollection<DocumentRepository> _children;

    public string Id { get; set; }
    public string Parent { get; set; }
    public string Text { get; set; }
    public string TenantId { get; set; }
    public string ApplicationId { get; set; }
    public string Path { get; set; }
    public string LinkPath { get; set; }
    public string Applications { get; set; }
    public DateTime CreatedOn { get; set; }

    public ObservableCollection<DocumentRepository> Children
    {
        get => _children;
        set
        {
            _children = value;
            OnPropertyChanged();
        }
    }
}

ViewModel

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<DocumentRepository> _items;

    public ObservableCollection<DocumentRepository> Items
    {
        get => _items;
        set
        {
            _items = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {

    }

    public void Start()
    {
        if (File.Exists("data.json"))
        {
            string json = File.ReadAllText("data.json");
            List<DocumentRepository> items = JsonConvert.DeserializeObject<List<DocumentRepository>>(json);
            LoadTree(items);
        }
    }

    private void LoadTree(List<DocumentRepository> items)
    {
        Items = new ObservableCollection<DocumentRepository>(items.Where(x => x.Parent == "#"));
        if (Items?.Count > 0)
        {
            foreach (DocumentRepository childNode in Items)
                LoadChildren(childNode, items);
        }
    }

    private void LoadChildren(DocumentRepository node, List<DocumentRepository> items)
    {
        node.Children = new ObservableCollection<DocumentRepository>(items.Where(x => x.Parent == node.Id));
        if (node.Children?.Count > 0)
        {
            foreach (DocumentRepository childNode in node.Children)
                LoadChildren(childNode, items);
        }
    }
}

View

<TreeView ItemsSource="{Binding Items}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:DocumentRepository}" ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Path}"/>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

Attach View Model

private MainViewModel VM;

public MainWindow()
{
    InitializeComponent();
    VM = new MainViewModel();
    DataContext = VM;
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    VM.Start();
}

enter image description here

Upvotes: 1

BionicCode
BionicCode

Reputation: 29018

This is just a basic example how you could build the tree from a JSON response. You may have to adjust criteria to map child to parent (it's a depth-first traversal opposed to my originally suggested breadth-first).

Note that if you know if and how the JSON response is ordered you can safe significant time by eliminating the searching of the root node and it's related nodes.

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty TreeDataProperty = DependencyProperty.Register(
    "TreeData",
    typeof(ObservableCollection<DocumentRepository>),
    typeof(MainWindow),
    new PropertyMetadata(default(ObservableCollection<DocumentRepository>)));

  public ObservableCollection<DocumentRepository> Tasks
  {
    get => (ObservableCollection<DocumentRepository>) GetValue(MainWindow.TreeDataProperty);
    set => SetValue(MainWindow.TreeDataProperty, value);
  }

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.TreeData = new ObservableCollection<DocumentRepository>();
  }

  public void ConvertJsonToTree(string jsonResponse)
  {
    List<DocumentRepository> allNodes = GetAllNodesFromJson(jsonResponse);
    IEnumerable<DocumentRepository> resultTree = BuildTree(allNodes);
    this.TreeData = new ObservableCollection<DocumentRepository>(resultTree);
  }

  private IEnumerable<DocumentRepository> BuildTree(List<DocumentRepository> allNodes)
  {
    var tree = new List<DocumentRepository>();

    while (allNodes.Any())
    {
      var rootNode = allNodes.First(node => node.Parent == null);
      allNodes.Remove(rootNode);

      CollectChildren(rootNode, allNodes);
      tree.Add(rootNode);
    }
    return tree;
  }

  private void CollectChildren(DocumentRepository parentNode, List<DocumentRepository> allNodes)
  {
    foreach (DocumentRepository childNode in allNodes.Where(node => node.Parent == parentNode.ParentId))
    {
      parentNode.Children.Add(childNode);
      allNodes.Remove(childNode);
      CollectChildren(childNode, allNodes);
    }
  }
}

MainWindeo.xaml

<Window>
  <Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type DocumentRepository}"
                              ItemsSource="{Binding Children}">
      <TextBlock Text="{Binding Text}" />
    </HierarchicalDataTemplate>
  </Window.Resources>

  <TreeView ItemsSource="{Binding TreeData}" />
</Window>

Upvotes: 1

Related Questions