Binding to a treeview programatically not working in UWP - c#

I'm following this article to try and programmatically bind data to a treeview (I'm on 1903).
In a brand new UWP app, I have the following code behind:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
treeView.ItemsSource = items;
stackPanel.Children.Add(treeView);
}
Item looks like this:
public class Item
{
public string Name { get; set; }
public ObservableCollection<Item> Children { get; set; } = new ObservableCollection<Item>();
public override string ToString()
{
return Name;
}
}
This appears to be the exact structure outlined in the above article. However, when I run the application, I get this:
My guess is that I need to do, or set something that tells this treeview, or the collection that it has children - but I can't see what that might be.

You should create an ItemTemplate as explained in the docs.
You could use the XamlReader class to do this programmatically. Something like this:
const string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><TreeViewItem ItemsSource=\"{Binding Children}\" Content=\"{Binding Name}\"/></DataTemplate>";
treeView.ItemTemplate = XamlReader.Load(Xaml) as DataTemplate;

If you use C# to build a TreeView, I recommend adding a TreeViewNode using traversal.
Due to the lack of instructions, TreeView does not automatically handle the Children of the Item. In the documentation you provide, the TreeView has a DataTemplate directive, so the children can render.
You can change code like this:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
foreach (var root in items)
{
var rootNode = new TreeViewNode() { Content = root.Name };
if (root.Children.Count > 0)
{
foreach (var child in root.Children)
{
rootNode.Children.Add(new TreeViewNode() { Content = child.Name });
}
}
treeView.RootNodes.Add(rootNode);
}
stackPanel.Children.Add(treeView);
}
Best regards.

Related

My code-generated ListBox's items isn't showing any content

I have a code-generated window in WPF, (using dotnet core 3.0 preview 6), and when running my application gets the correct data, and the ListBox is populated with the correct number of rows, but none of them contains values
This is a test-project I'm doing to get familiar with code generated WPF as it's needed for an upcoming project we're doing at work; I would have preferred using XAML but my lead says that that will create issues with code-re-usability.
I at first made sure I used am object which is "clean", (my entities are setup for Linq2db, so I ensured that the attributes couldn't be the culprit), then I tested binding, (just got the "Error 40" -error code, but that isn't relevant to the main problem). I have also changed the type of boxes, but It does not help, (DataGrid did work but it's not what I'm looking for in a visual).
public class ChatWindow : IChatWindow
{
private ObservableCollection<MessageDto> _observableMessages;
private readonly IMessagesRepository _messagesRepository;
public ChatWindow(IMessagesRepository messagesRepository)
{
_messagesRepository = messagesRepository;
Task.Run(async () => { await Updater(); }).ConfigureAwait(false);
}
public async Task ShowAsync(User user)
{
var chatLog = new ListBox()
{
Name = "Chatview",
ItemsSource = _observableMessages,
ItemTemplate = new DataTemplate(typeof(MessageDto)),
DataContext = _observableMessages
};
//var myBinding = new Binding("_observableMessages");
//myBinding.Source = _observableMessages;
//chatLog.SetBinding(ListBox.ItemsSourceProperty, myBinding);
var input = new TextBox()
{
Name = "InputField",
Background = new SolidColorBrush(Color.FromRgb(35, 35, 35))
};
var stackPanel = new StackPanel()
{
Children =
{
chatLog,
input
}
};
var window = new Window()
{
Name = "ChatWindow",
Content = stackPanel,
};
window.Show();
}
private async Task Updater()
{
while (true)
{
var messages = await _messagesRepository.GetAllMessages(1);
_observableMessages = new ObservableCollection<MessageDto>(messages.Select(m => new MessageDto()
{
Timestamp = m.Timestamp,
From = m.From,
Message = m.Message
}));
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
class MessageDto
{
public DateTime Timestamp { get; set; }
public long From { get; set; }
public string Message { get; set; }
}
Image of the resultant window, (some styling code was removed from the example code to reduce noise)
Based on Flithor's comment, i did this and it worked perfectly:
private DataTemplate GetDataTemplate()
{
var dataTemplate = new DataTemplate(typeof(MessageDto));
FrameworkElementFactory stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory timestamp = new FrameworkElementFactory(typeof(Label));
timestamp.SetBinding(Label.ContentProperty, new Binding("Timestamp"));
FrameworkElementFactory from = new FrameworkElementFactory(typeof(Label));
from.SetBinding(Label.ContentProperty, new Binding("From"));
FrameworkElementFactory message = new FrameworkElementFactory(typeof(Label));
message.SetBinding(Label.ContentProperty, new Binding("Message"));
stackPanelFactory.AppendChild(timestamp);
stackPanelFactory.AppendChild(from);
stackPanelFactory.AppendChild(message);
dataTemplate.VisualTree = stackPanelFactory;
return dataTemplate;
}

Xamarin Forms add input field to each item of listview

I'm new to Xamarin Forms and I would want to add a input field (Numeric) on the right side of each item in my listview, so that I can type a quanty to my items. Somthing like a shopping list of items with the quantity of how many you want.
here is my code of my listview
List<string> item = new List<string>();
public MainPage()
{
InitializeComponent();
item.Add("Apple");
item.Add("Banana");
item.Add("Graps");
item.Add("Orange");
item.Add("Pineapple");
item.Add("Strawberry");
item.Add("Lemon");
item.Add("Mango");
item.Add("Cherry");
item.Add("Watermelon");
item.Add("Add");
var listView = new ListView
{
RowHeight = 40
};
listView.ItemsSource = item;
StackLayout layout = new StackLayout();
layout.Children.Add(listView);
listView.ItemSelected += (sender, e) =>
{
if (e.SelectedItem.ToString() == "Add")
{
var MyEntry = new Entry { Placeholder = "new item" };
layout.Children.Add(MyEntry);
MyEntry.Completed += MyEntry_Completed;
}
};
this.Content = layout;
}
private void MyEntry_Completed(object sender, EventArgs e)
{
var text = ((Entry)sender).Text;
item.Add(text);
}
I had a lot of trouble wrapping my head around databinding from the C# side in Xamarin Forms. I would recommend you checkout DataBinding to get a better understanding of this. I created a custom ViewCell for you to play around with, and a basic implementation so you can get a visual of how to implement it. You can adjust the view cell as much as needed. I just wanted to give you a bases of how to implement it. Hope this helps.
ViewCell
This is the view that displays for each list item in the list view.
using Xamarin.Forms;
namespace BountyApp.Controls
{
public class CustomViewCell : ViewCell
{
private Grid _grid = new Grid();
private Label _lbl = new Label() { HorizontalTextAlignment = TextAlignment.End, VerticalTextAlignment = TextAlignment.Center };
private Entry _entry = new Entry();
public CustomViewCell()
{
_lbl.SetBinding(Label.TextProperty, new Binding("Title"));
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.3, GridUnitType.Star) });
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.7, GridUnitType.Star) });
_grid.Children.Add(_lbl, 0, 0);
_grid.Children.Add(_entry, 1, 0);
View = _grid;
}
}
}
Implementing ViewCell
using Xamarin.Forms;
namespace BountyApp.Pages
{
public class ViewModel
{
public string Title { get; set; }
}
public class StepperPage : ContentPage
{
public ObservableCollection<ViewModel> List { get; set; }
public StepperPage()
{
List = new ObservableCollection<ViewModel>();
List.Add(new ViewModel { Title = "Apple" });
List.Add(new ViewModel { Title = "Banana" });
List.Add(new ViewModel { Title = "Graps" });
List.Add(new ViewModel { Title = "Orange" });
List.Add(new ViewModel { Title = "Pineapple" });
List.Add(new ViewModel { Title = "Strawberry" });
var listView = new ListView
{
RowHeight = 40,
ItemTemplate = new DataTemplate(typeof(CustomViewCell)),
ItemsSource = List
};
Content = listView;
}
}
}
Result

Expandable ListView in Xamarin Forms

I have the following code to display a ListView using Xamarin Forms:
App.cs
public App ()
{
MainPage = GetMainPage();
}
public static Page GetMainPage()
{
return new NavigationPage(new DrilldownListViewByItem());
}
DrilldownListViewByItem:
public class DrilldownListViewByItem : ContentPage
{
public DrilldownListViewByItem()
{
Title = "Drilldown List Using ListView";
var listView = new ListView();
listView.ItemsSource = new ListItem[] {
new ListItem {Title = "First", Description="1st item"},
new ListItem {Title = "Second", Description="2nd item"},
new ListItem {Title = "Third", Description="3rd item"}
};
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Title");
listView.ItemTapped += async (sender, args) =>
{
var item = args.Item as ListItem;
//if (item == null) return;
//await Navigation.PushAsync(new DetailPage(item));
//listView.SelectedItem = null;
};
Content = listView;
}
}
ListItem has these properties:
public string Title { get; set; }
public string Description { get; set; }
What I want to do is to show subitems when I tab on one of the main items. These subitems should be hidden at the beggining, but they should be there, I don't want to load them every time I tab on an item. Any idea? Thanks!
Try changing the visibility of the items you want to appear and disappear based on the click event of the list item.
On iOS you may also have to change the cell height (using HeightRequest), because it is likely that it won't change on it's own. I am guessing that android will work just fine.
Hope this helps.

ListBox selected item refer to a variable

Here what I've done.
I made my own class.
public class Node
{
public string name;
public string type;
public string vm_name;
public string vm_ip;
public string vm_hostname;
}
string[] nodes = new string[2];
Node vm1 = new Node();
Node vm2 = new Node();
I set Name property:
vm1.name = "name1";
vm2.name = "name2";
I put all variables from this type in a string
nodes[0] = vm1.name;
nodes[1] = vm2.name;
After that I added that array into the listbox items
nodeList.Items.AddRange(nodes);
Is there a way to access the variable by selecting the item from the list box ?
If there is a better way to do it I am open for suggestions.
Use DisplayMember and DataSource properties.And create an array of Nodes instead of strings,
var nodes = new []
{
new Node { name = "name1" },
new Node { name = "name2" }
}
nodeList.DisplayMember = "name";
nodeList.DataSource = nodes;
Then you can access your SelectedItem and cast it to Node like this:
private void listBox_SelectedIndexChanged(object sender, EventArgs e)
{
var selectedNode = nodeList.SelectedItem as Node;
if (selectedNode != null)
{
...
}
}
Bind the listbox directly to the Node instance instead of a string.
var node = (Node) nodeList.SelectItem;

Windows Forms TreeView - Bind hierarchical recursive datasource

I'm converting a WPF client in Windows Forms and I got some problems trying to replicate the TreeView control structure.
In the first project I have a custom factory that builds a structure starting from an input string that is basically a XML.
The return type is a collection.
Custom TreeNode:
public class TreeViewNode
{
public TreeViewNode() { }
public DocumentKey DocKey { get; set; }
public string Text { get; set; }
public IList<TreeViewNode> Children { get; set; }
}
Factory:
public class TreeViewFactory
{
public IList<TreeViewNode> GetSctructure(DocumentKey docKey, string structure, bool loadAllParents)
{
XDocument xmlDocstructure = CommonXmlValueParser.GetXDocument(structure);
var parentsNodes = (from item in xmlDocstructure.Descendants("structure_item")
where (CommonXmlValueParser.GetAttribute(item, "level") == "1")
select new TreeViewNode
{
Text = GetNodeText(item),
DocKey = new DocumentKey()
{
Bank = docKey.Bank,
Ud = int.Parse(CommonXmlValueParser.GetElement(item.Element("ud"))),
Master = int.Parse(CommonXmlValueParser.GetElement(item.Element("master"))),
NVig = int.Parse(CommonXmlValueParser.GetElement(item.Element("nvig"))),
Subjects = docKey.Subjects
},
Children = GetChildrenNodes(item, 2, docKey.Bank)
}).ToList();
return parentsNodes;
}
private IList<TreeViewNode> GetChildrenNodes(XElement element, int level, int dataBank)
{
var childrenNodes = (from item in element.Descendants("structure_item")
where (CommonXmlValueParser.GetAttribute(item, "level") == level.ToString())
select new TreeViewNode
{
Text = GetNodeText(item),
DocKey = new DocumentKey()
{
Bank = dataBank,
Ud = int.Parse(CommonXmlValueParser.GetElement(item.Element("ud"))),
Master = int.Parse(CommonXmlValueParser.GetElement(item.Element("master"))),
NVig = int.Parse(CommonXmlValueParser.GetElement(item.Element("nvig"))),
},
Children = GetChildrenNodes(item, level + 1, dataBank)
}).ToList();
return childrenNodes;
}
}
Binding:
void CreateTree(object tree, EventArgs e)
{
//...
TreeViewFactory treeFactory = new TreeViewFactory();
var documentStructure = treeFactory.Structure(document.DocumentKey, document.XmlStructure, true);
this.tabMainControl.document.SetTreeViewStructureNodes(documentStructure);
}
public void SetTreeViewStructureNodes(IList<TreeViewNode> nodes)
{
this.treeView.ItemsSource = nodes;
}
Update:
I made the TreeViewNode derive from TreeNode and changed the method SetTreeViewStructureNodes in:
private TreeView SetTreeViewStructureNodes(IList<TreeViewNode> nodes)
{
TreeView treeView = new TreeView();
treeView.Nodes.AddRange(nodes.ToArray());
return treeView;
}
still that doesn't achieve my goal as it's still not rendered...
In Windows Forms as far as I know it's not possible to associate a sort of datasource that is a whatever type collection (implements IEnumerable).
Apart from using 3rd party components, how can I solve my problem. My experience on WinForms is pretty short and just when I learnt to manage much better WPF they decided to shift it :(
Appreciate all your help, regards.
Update2:
Piece of WinForms User Control where treeView is filled:
TreeView treeView = (TreeView)documentViewControl.Controls["treeViewStructure"];
TreeViewFactory treeFactory = new TreeViewFactory();
var documentStructure = treeFactory.GetStructure(document.DocumentKey, document.XmlStructure, true);
treeView = this.SetTreeViewStructureNodes(documentStructure);
Basically I'm moving from an UC to another. Both of them are part of 2 Tabs, children of a TabControl.
(Answered by the OP as a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Actually I got that on my own. The idea is to mutuate the ricursive idea, creating one TreeNode from the collection (IList) of TreeViewNodes. Problem 1: recursion Problem 2: how to mantain the DocKey custom property
private TreeNode[] GetTreeViewNodes(IList<TreeViewNode> nodes)
{
IList<TreeNode> returnedNodes = new List<TreeNode>();
foreach (var item in nodes)
{
TreeNode node = new TreeNode(item.Text, this.GetTreeViewNodes(item.Children));
node.Tag = item.DocKey;
returnedNodes.Add(node);
}
return returnedNodes.ToArray();
}
And the code required for the treeview becomes this one:
this.treeView.Nodes.Clear();
this.treeView.Nodes.AddRange(this.GetTreeViewNodes(documentStructure));

Categories