Extending TreeView with Extra Properties? - c#

I'm working on adding some properties to an extension class of TreeView. I need the extra fields for context when one of the items in the tree is clicked. I can't seem to get the tree view to show any of the data I'm giving it.
In my MainView.cs I'm simply setting the items source as such:
TreeMenu.ItemsSource = (an ObservableCollection of ParentItems)
XAML:
<Grid x:Name="TreeGrid" Width="350" HorizontalAlignment="Left">
<TreeView Name="TreeMenu" Background="Red" Foreground="Black">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:ParentItem}" ItemsSource="{Binding ChildItems}">
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
Object model:
public class ParentItem : TreeViewItem, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
public ParentItem()
{
_text = "";
_url = "";
_childItems = new ObservableCollection<ChildItem>();
}
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
NotifyPropertyChanged("Text");
}
}
private string _url;
public string URL
{
get
{
return _url;
}
set
{
_url = value;
NotifyPropertyChanged("URL");
}
}
private ObservableCollection<ChildItem> _childItems;
public ObservableCollection<ChildItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
NotifyPropertyChanged("ChildItems");
}
}
}
Note that ChildItem is almost identical to ParentItem, minus the collection object. Originally I tried extending TreeNode in my object classes, but that had the same issue.
Does anyone know why my TreeView won't appear? Am I missing something while extending TreeView?

No point in extending TreeViewItem.
We don't see how you assign your collection so they might something done wrongly.
This does work:
Code
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemCollection
{
new Item
{
Text = "A",
Items = new ItemCollection
{
new Item
{
Text = "B",
Items = new ItemCollection
{
new Item
{
Text = "C"
}
}
}
}
}
};
}
}
public class Item
{
public string Text { get; set; }
public ItemCollection Items { get; set; }
}
public class ItemCollection : ObservableCollection<Item>
{
}
}
XAML
<Window x:Class="WpfApplication4.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:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="local:Item" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Result

Related

WPF ItemsControl can't bind Command

I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.

How to bind listview to custom item collection

How do I get my custom item collection to show up in my list view using WPF data bindings?
I have a tried to make a ViewModel and a custom collection that the ViewModel manipulates, in an attempt to get this collection to show up in a listview. I have a view model and a custom collection and a custom item class:
public class TranslationViewModel
{
public TranslationViewModel() { this.translatedItems = new TransListboxCollection(); }
public TransListboxCollection translatedItems { get; private set; }
public void addTranslatedItem(TransListboxItem message)
{
translatedItems.Add(message);
}
}
public class TransListboxCollection : BindingList<TransListboxItem>
{
public TransListboxCollection()
{
//initialize
}
}
public class TransListboxItem : INotifyPropertyChanged
{
private String _rawString;
private String _tString;
public String rawString
{
get { return _rawString; }
set { _rawString = value; NotifyPropertyChanged("rawString"); }
}
public String tString
{
get { return _tString; }
set { _tString = value; NotifyPropertyChanged("tString"); }
}
public TransListboxItem(String value)
{
this.tString = value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return this.tString;
}
}
I have a WPF element hosted in a windows form
public partial class wGlobal : UserControl
{
public TranslationViewModel tvm { get; set; }
public wGlobal()
{
InitializeComponent();
this.DataContext = tvm;
}
}
The XAML code for such
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MHF_Transcoder_3" x:Class="MHF_Transcoder_3.wGlobal"
mc:Ignorable="d"
d:DesignWidth="1000" d:DesignHeight="150">
<Grid Width="1000" Height="150">
<ListView x:Name="listView1" ItemsSource="{Binding tvm}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView>
</Grid>
and I have that element hosted in a windows form control
public partial class frmGlobal : Form
{
wGlobal xamlForm;
TranslationViewModel tvm;
public frmGlobal()
{
InitializeComponent();
tvm = new TranslationViewModel();
xamlForm = (wGlobal)elementHost1.Child;
xamlForm.tvm = tvm;
}
delegate void addMessageCallback(TransListboxItem message);
public void addMessage(TransListboxItem message) {
tvm.addTranslatedItem(message);
}
}
When I get the program up and launch everything, all my list view says is "System.Windows.DataTemplate". I've never really worked with WPF or data bindings before. I'm open to any and all advice and suggestions. Please help me get this setup and properly working.
Wrap Datatemplate with ItemTemplate
<ListView x:Name="listView1" ItemsSource="{Binding translatedItems}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Also as the tvm is the datacontext, you must bind to the collection "translatedItems"

Data Template not shown

A listbox data template doesn't show and I cannot figure out why.
If I don't use a DataTemplate and copy the contents into the control section itself, it's fine.
I don't do very much binding in XAML, I usually do it all in code. What did I do wrong?
XAML
<UserControl x:Class="Cis.CustomControls.CisArrivalsPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="296" Width="876">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<ListBoxItem>
<StackPanel>
<TextBlock Background="Blue" Text="{Binding Path=StationName}" />
<TextBlock Background="Brown" Text="{Binding Path=ArrivalPlatform}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Width="487" Margin="0,66,0,33" ItemTemplate="{StaticResource DataTemplate}">
</ListBox>
</StackPanel>
</Grid>
</UserControl>
CS
public partial class CisArrivalsPanel : UserControl
{
public CisArrivalsPanel()
{
InitializeComponent();
this.DataContext = new ArrivalRowItem();
}
}
Model
public class ArrivalRowItem : INotifyPropertyChanged
{
public ArrivalRowItem()
{
this.StationName = "Lincoln";
this.ArrivalPlatform = "1";
}
private string _stationName;
public string StationName
{
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private string _arrivalPlatform;
public string ArrivalPlatform
{
get
{
return _arrivalPlatform;
}
set
{
_arrivalPlatform = value;
NotifyPropertyChanged("ArrivalPlatform");
}
}
private DateTime _arrivalDateTime;
public DateTime ArrivalDateTime
{
get
{
return _arrivalDateTime;
}
set
{
_arrivalDateTime = value;
NotifyPropertyChanged("ArrivalDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You have everything set up, but you don't actually have any data.
ListBox, like other ItemsControls acts against a collection of data, and generates an instance of the template for each item it finds.
Given that you haven't set ItemsSource or populated any collection I can see, you need to create a collection (probably an ObservableCollection) and set the ItemsSource to it via binding. Then add some items to it, and the ListBox will display them!

Header text shifted in TreeView control

I am playing with WPF TreeView control and facing strange problem.
When I add TreeView items programmatically all working correct, but in case I have added items through binding - I have Items with gap in header:
furthermore, this gap is only one "sensitive" part of the TreeItem's line. I cant select item by clicking on the text, it can be selected only if I click on the gap area.
Here is my code:
namespace WPFTreeViewExperience
{
/// <summary>
/// Data item interface
/// </summary>
public interface IMyTree
{
string Title { get; set; }
List<IMyTree> Items { get; set; }
}
/// <summary>
/// Hierarchical data model
/// </summary>
public class MyTreeLevel : IMyTree
{
public string Title { get; set; }
public List<IMyTree> Items { get; set; }
public MyTreeLevel(string Title, params MyTreeLevel[] Items)
{
this.Title = Title;
this.Items = new List<IMyTree>();
foreach (MyTreeLevel item in Items)
{
this.Items.Add(item);
}
}
}
/// <summary>
/// Viewmodel
/// </summary>
public class MyTreeViewModel : INotifyPropertyChanged
{
private void NotifyChanges(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
private List<MyTreeLevel> _Tree;
public List<MyTreeLevel> MyTree
{
get
{
return _Tree;
}
set
{
_Tree = value;
NotifyChanges("MyTree");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MyTreeViewModel()
{
List<MyTreeLevel> simpleTree = new List<MyTreeLevel>();
simpleTree.Add(new MyTreeLevel("1-0", new MyTreeLevel("1-1"),
new MyTreeLevel("1-2",
new MyTreeLevel("1-2-1"),
new MyTreeLevel("1-2-2")
),
new MyTreeLevel("1-3")));
simpleTree.Add(new MyTreeLevel("2-0", new MyTreeLevel("2-1"),
new MyTreeLevel("2-2",
new MyTreeLevel("2-2-1"),
new MyTreeLevel("2-2-2"),
new MyTreeLevel("2-3"))));
MyTree = simpleTree;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TreeExample.DataContext = new MyTreeViewModel();
}
}
}
and my XAML code:
<Window x:Class="WPFTreeViewExperience.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewExperience"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView Name="TreeExample" ItemsSource="{Binding MyTree}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:MyTreeLevel}">
<TreeViewItem Header="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
What is wrong with my binding?
I think that ItemTemplate should be something like this:
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:MyTreeLevel}">
<Grid>
<TextBlock Margin="0" Text="{Binding Path=Title}" />
</Grid>
</HierarchicalDataTemplate>

How can I delay the call to a property in a list?

One of the properties in a list take a long time to load (creates a thumbnail on the fly). How can I display the rest of the properties on a list and load the long processing one in the background.
The following example shows the situation. I'd like to be able to show the short name immediately and the long names as they become available.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var list = new List<Example> {
new Example {ShortName = "A", LongName = "Z"},
new Example {ShortName = "B", LongName = "ZZ"},
new Example {ShortName = "C", LongName = "ZZZ"}};
DataContext = list;
}
}
public class Example : INotifyPropertyChanged
{
private String _shortName;
public String ShortName
{
get { return _shortName; }
set
{
if (_shortName == value) return;
_shortName = value;
NotifyPropertyChanged("ShortName");
}
}
private String _longName;
public String LongName
{
get
{
System.Threading.Thread.Sleep(1000);
return _longName;
}
set
{
if (_longName == value) return;
_longName = value;
NotifyPropertyChanged("LongName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label>ShortName: </Label>
<Label Content="{Binding ShortName}" />
<Label> LongName:</Label>
<Label Content="{Binding LongName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You can load the property asynchronously using the IsAsync binding property:
<Label Content="{Binding Path=LongName,IsAsync=true}" />
You can also use the Fallback property to display a message like Loading until the real value gets populated.

Categories

Resources