Binding hierarchical data with templates using a generic Tree data structure - wpf

How to bind a Tree data structure using hierarchical data template to a tree list control. The difference is that instead of creating different types for each level in hierarchy I intend to use only one type “Node” differentiated by the “Type” enumeration indicating its level in the hierarchy. Is this a feasible approach. How to display the data using TreeListControl.
public class TreeNode<T> where T : new()
{
public TreeNode<T> Parent { get; set; }
public IList<TreeNode<T>> Children { get; set; }
protected TreeNodeType Type { get; set; }
public T Current { get; set; }
public TreeNode()
{
}
public TreeNode(TreeNodeType type)
{
this.Type = type;
this.Current = new T();
this.Children = new List<TreeNode<T>>();
}
public void AddChildren(TreeNode<T> child)
{
child.Parent = this;
this.Children.Add(child);
}
public override string ToString()
{
return string.Format("Type :{0} Name :{1}", this.Type, this.Current);
}
}
/// <summary>
/// Tree node type
/// </summary>
public enum TreeNodeType
{
Manager = 0,
Employee,
}
public class EmployeeNode
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}

You're going to need to use a DataTemplateSelector. MSDN describes how to use one. Even though the example is for a ListBox, you can do this with a TreeView.

I created a data selector that always returns the same template. This will return the same template for each node of the self referencing data.
public class HierarchialDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
return element.FindResource("HierarchialDataTemplate") as DataTemplate;
}
return null;
}
}

Related

How to get the number of rows in a row template selector - wpf - gridcontrol

I need to get the row-count of a GridControl within its RowTemplateSelector in order to change the rows template based on that number.
I am trying to use the container field passed to the Select() method of the TemplateSelector.
You don't need the conatiner-object - check out this sample from the DX-docs:
public class RowTemplateSelector : DataTemplateSelector
{
public DataTemplate EvenRowTemplate { get; set; }
public DataTemplate OddRowTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
RowData row = item as RowData; //<= mind this line of code!!!!
if (row != null)
return row.EvenRow ? EvenRowTemplate : OddRowTemplate;
return base.SelectTemplate(item, container);
}
}
Using the RowData-object you can access the corresponding View-object
DataViewBase view = row.View;
Using the View-object you can access the corresponding Grid-object
DataControlBase grid = view.DataControl;
Having access to the DataControl means you have access to its item-source
object o = grid.ItemsSource;
From there its a matter of casting and counting your actual type of ItemsSource.
The following TemplateSelector returns different Templates depending on wether the item-count is smaller or bigger then ten:
public class RowTemplateSelector : DataTemplateSelector
{
public DataTemplate SmallerThenTenTemplate { get; set; }
public DataTemplate BiggerThenTenTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
RowData row = item as RowData; //<= mind this line of code!!!!
object itemSource = row.View.DataControl.ItemsSource;
IEnumerable<YourModelType> sourceList = (IEnumerable<YourModelType>)itemSource;
if (sourceList.Count() > 10)
return BiggerThenTenTemplate;
else
return SmallerThenTenTemplate;
}
}

Execute RaiseCanExecuteChanged from 'subclass'

So I have the following setup:
PLANNING:
public class Planning : ViewModelBase
{
public Planning()
{
AddNewActivityCommand = new RelayCommand(AddActivity, CanAddActivity);
}
public ObservableCollection<PlanningItem> PlanningItems { get; set; }
public PlanningItem SelectedPlan { get; set; }
#region AddNewActivity
public RelayCommand AddNewActivityCommand { get; private set; }
private bool CanAddActivity()
{
if (!PlanningItems.Any())
{
return true;
}
if (string.IsNullOrEmpty(PlanningItems[PlanningItems.Count - 1].Activities) != true ||
PlanningItems[PlanningItems.Count - 1].DhpRepresentativeSelected != null)
{
return true;
}
return false;
}
private void AddActivity()
{
PlanningItems.Add(new PlanningItem());
AddNewActivityCommand.RaiseCanExecuteChanged();
}
#endregion
}
PLANNING ITEM:
public class PlanningItem : ViewModelBase
{
private string _activity;
public ObservableCollection<OutreachUser> DhpRepresentativeSource
{
get
{
var userSource = new ObservableCollection<OutreachUser>();
using (var context = new Outreach_Entities())
{
var query = from a in context.UserInfoes
join b in context.PersonalInfoes on a.UserIdentity equals b.PersonIdentity
join c in context.PersonalTitles on b.TitleLink equals c.TitleIdentity into cGroup
from c in cGroup.DefaultIfEmpty()
select new OutreachUser
{
PersonLink = a.UserIdentity,
Username = a.Username,
FirstName = b.FirstName,
MiddleInitial = b.MiddleInitial,
LastName = b.LastName
};
foreach (var result in query)
{
userSource.Add(result);
}
return userSource;
}
}
}
public OutreachUser DhpRepresentativeSelected { get; set; }
public DateTime PlanningDate { get; set; }
public TimeSpan PlanningStart { get; set; }
public TimeSpan PlanningEnd { get; set; }
public int PlanningTotalHours { get; set; }
public string Activities
{
get
{
return _activity;
}
set
{
_activity = value;
RaisePropertyChanged(nameof(Activities), "", _activity, true);
}
}
}
I have a ListBox bound to the PlanningItems Observable Collection.
I want to be able to add a new item to the list if the following criteria are met:
The Planning Items Collection is empty.
The last item in the Planning Items Collection has a DhpRepresentativeSelected that is not null.
The last item in the Planning Items Collection has some text in the Activities string.
The first item is easy enough because I call AddNewActivityCommand.RaiseCanExecuteChanged(); after I add a new item from an empty list.
Now I need to call the AddNewActivityCommand.RaiseCanExecuteChanged(); from within the PlanningItem ViewModel, but it does not have access rights to the command.
Clueless pointed me to the answer.
What I did was inside of my Planning ViewModel I created an internal Method that called the AddNewActivityCommand.RaiseCanExecuteChanged() method. I think called that method from within the PlanningItems ViewModel.

MVVM: how to create a ViewModel from Model object

I want to get values of my model and create a viewmode
In my Model I have
public class TestElement
{
public TestElement CurrentNode { get; set; }
public TestElement Parent { get; set; }
}
I have some method that do this
if (thisNode == null)
{
thisNode = new TestElement { Name = name, Parent = CurrentNode };
currentCollection.Add(thisNode);
}
In my view model I want to create TestElementViewModel Parent and get my model Parent values
public class TestElementViewModel
{
public TestElementViewModel Parent { get; set; }
I want to use it in this method
public IEnumerable<TestElementViewModel> ToTreeViewModel(IEnumerable<TestElement> treemodel)
{
foreach (TestElementitem in treemodel)
yield return new TestElementViewModel
{
Id = item.Id,
Name = item.Name,
Children = ToTreeViewModel(item.Children).ToList(),
Parent = item.Parent
};
}
}
How can I achieve that?
I'm guessing your casting error occurs on the the line
Parent = item.Parent
Well the Parent property in your TestElementViewModel isn't a TestElement type so you can't do that.
Try assigning a new TestElementViewModel instead.
Parent = new TestElementViewModel { Id = item.Parent.Id, Name = item.Parent.Name, ... }
One improvement you might want to consider is using wrappers in your ViewModel class, which will make assigning properties a little easier.
For example,
public class TestElementViewModel : INotifyPropertyChanged
{
public TestElementViewModel(TestElement model)
{
Model = model;
if(Model.Parent != null)
Parent = new TestElementViewModel(Model.Parent);
}
public TestElement Model { get; private set; }
private TestElementViewModel _parent;
public TestElementViewModel Parent
{ get { return _parent; }
set { _parent = value; OnPropertyChanged("Parent"); }
}
public int Id
{
get { return Model.Id; }
set { Model.Id = value; OnPropertyChanged("Id"); }
}
// rest of the properties need wrapping too
}
makes it so that you don't have to manually assign the properties each time you instantiate a new viewmodel.

WPF databinding to a linked list

I want to databind a ListBox to a linked list
public class MyClass
{
public string MyText{ get; set; }
public MyClass PreviousItem{ get; set; }
}
I want to use an instance of MyClass as the datasource of a ListBox, to basically show a list of the MyClass instance and all it's PreviousItems.
Of course binding to an instance of MyClass will result in only the topmost parent being shown. What would be the best approach for this?
Why do you need a custom implementation of a LinkedList in the first place? There is a .NET implementation already: System.Collections.Generic.LinkedList
Other than that, you have basically three options:
Recommended: If it fits your
business logic, impement at least
IEnumerable in MyClass (like the .NET lists)
Create a ViewModel which traverses
your items of MyClass and
puts them into an
ObservableCollection
Create an IValueConverter object to
convert your linked list to a
collectionType like
ObservableCollection
You can use BCL LinkedList<T> class. Or alternatively if you like your class very much you can implement IEnumerable like this
public class MyClass : IEnumerable<MyClass>
{
public string MyText { get; set; }
public MyClass PreviousItem { get; set; }
public IEnumerator<MyClass> GetEnumerator()
{
var item = this;
do
{
yield return item;
item = item.PreviousItem;
} while (item != null);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Actually implementing IEnumerable would suffice:
public IEnumerable<MyClass> TraverseItemsFromCurrent
{
get
{
var current = this;
yield return current;
while (current.PreviousItem != null)
{
current = current.PreviousItem;
yield return current;
}
}
}
And yes, using LinkedList<T> might be easier and more adequate to your purpose.
The easiest thing (in my opinion) is to implement IEnumerable
public class MyClass : IEnumerable<MyClass>
{
public string MyText { get; set; }
public MyClass PreviousItem { get; set; }
IEnumerator<MyClass> IEnumerable<MyClass>.GetEnumerator()
{
for (var item = this; item.PreviousItem != null; item = item.PreviousItem)
yield return item;
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable<MyClass>)this).GetEnumerator();
}
}
Then you code would look like this
public Window1()
{
MyClass item1 = new MyClass() {MyText = "No1"};
MyClass item2 = new MyClass() {MyText = "No2"};
MyClass item3 = new MyClass() {MyText = "No3"};
MyClass item4 = new MyClass() {MyText = "No4"};
item4.PreviousItem = item3;
item3.PreviousItem = item2;
item2.PreviousItem = item1;
DataContext = item4; // your first item
}

WPF: Stop Binding if a UI element is not visible

Can I delay binding of a ui element if the element is not currently visible. Sometimes I have a form that has some hidden/minimised elements, I would like to not update them if they are not on the screen. I suspect the answer is no, but it never hurts to ask?
The answer is no because the binding might be cause of making an element visible again. So if binding did not work on hidden controls it would not allow the binding to make it visible again.
I know this is an old question, but as I failed to find an implemented class or something, I did it myself, following #Nir answer.
This is a Markup Extension that wraps normal binding to only really bind when the object IsVisible property becomes true for the first time:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace MakupExtensions {
[MarkupExtensionReturnType(typeof(object))]
public class LazyBindingExtension : MarkupExtension {
public LazyBindingExtension() {
}
public LazyBindingExtension(PropertyPath path) : this() {
Path = path;
}
public IValueConverter Converter {
get;
set;
}
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo ConverterCulture {
get;
set;
}
public object ConverterParamter {
get;
set;
}
public string ElementName {
get;
set;
}
[ConstructorArgument("path")]
public PropertyPath Path {
get;
set;
}
public RelativeSource RelativeSource {
get;
set;
}
public object Source {
get;
set;
}
public UpdateSourceTrigger UpdateSourceTrigger {
get;
set;
}
public bool ValidatesOnDataErrors {
get;
set;
}
public bool ValidatesOnExceptions {
get;
set;
}
public bool ValidatesOnNotifyDataErrors {
get;
set;
}
private Binding binding;
private DependencyObject bindingTarget;
private DependencyProperty bindingTargetProperty;
public override object ProvideValue(IServiceProvider serviceProvider) {
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider != null) {
bindingTarget = valueProvider.TargetObject as DependencyObject;
bindingTargetProperty = valueProvider.TargetProperty as DependencyProperty;
if (bindingTargetProperty == null || bindingTarget == null) {
throw new NotSupportedException($"The property '{valueProvider.TargetProperty}' on target '{valueProvider.TargetObject}' is not valid for a LazyBinding. The LazyBinding target must be a DependencyObject, and the target property must be a DependencyProperty.");
}
binding = new Binding {
Path = Path,
Converter = Converter,
ConverterCulture = ConverterCulture,
ConverterParameter = ConverterParamter
};
if (ElementName != null) {
binding.ElementName = ElementName;
}
if (RelativeSource != null) {
binding.RelativeSource = RelativeSource;
}
if (Source != null) {
binding.Source = Source;
}
binding.UpdateSourceTrigger = UpdateSourceTrigger;
binding.ValidatesOnDataErrors = ValidatesOnDataErrors;
binding.ValidatesOnExceptions = ValidatesOnExceptions;
binding.ValidatesOnNotifyDataErrors = ValidatesOnNotifyDataErrors;
return SetBinding();
}
return null;
}
public object SetBinding() {
var uiElement = bindingTarget as UIElement;
if (uiElement != null && !uiElement.IsVisible) {
uiElement.IsVisibleChanged += UiElement_IsVisibleChanged;
}
else {
ConsolidateBinding();
}
return bindingTarget.GetValue(bindingTargetProperty);
}
private void ConsolidateBinding() => BindingOperations.SetBinding(bindingTarget, bindingTargetProperty, binding);
private void UiElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) {
var uiElement = sender as UIElement;
if (uiElement != null && uiElement.IsVisible) {
uiElement.IsVisibleChanged -= UiElement_IsVisibleChanged;
ConsolidateBinding();
}
}
}
}
To use:
<ItemsControl ItemsSource="{mx:LazyBinding Documents}"/>
In this example, it will only bind when the ItemsControl IsVisible becomes true for the first time.
It will not unbind when the IsVisible gets false again, but I think someone can change it as needed.
There is no built in way to do this - but you can write it yourself.
The trick is to wrap binding in your own markup extension that uses the original binding but adds new behavior around it (for example, by setting UpdateSourceTrigger to Explicit when you don't want the binding to work.
Here's an example (that delays the binding's data transfer):
http://www.paulstovell.com/wpf-delaybinding
Now, there are a lot of possible edge conditions with disabling bindings for invisible controls, especially around showing and hiding controls, so I wouldn't write a generic extension for this - but maybe in your specific application this can be useful.
For a workaround I have a binding to the visibility of the object, when the object is set to visible, the property triggers the construction of the element behind it which have a binding via a ContentPresenter.
Improved MarkupExtension that wraps normal Binding to auto bind/unbind data model if visible changed.
See previous version here.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace UtilsWPF
{
[MarkupExtensionReturnType(typeof(object))]
public class LazyBindingExtension : MarkupExtension
{
public LazyBindingExtension()
{ }
public LazyBindingExtension(PropertyPath path) : this()
{
Path = path;
}
#region Properties
public IValueConverter Converter { get; set; }
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo ConverterCulture { get; set; }
public object ConverterParamter { get; set; }
public string ElementName { get; set; }
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public RelativeSource RelativeSource { get; set; }
public object Source { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
public bool ValidatesOnDataErrors { get; set; }
public bool ValidatesOnExceptions { get; set; }
public bool ValidatesOnNotifyDataErrors { get; set; }
private Binding binding;
private UIElement bindingTarget;
private DependencyProperty bindingTargetProperty;
#endregion
#region Init
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider != null)
{
bindingTarget = valueProvider.TargetObject as UIElement;
if (bindingTarget == null)
{
throw new NotSupportedException($"Target '{valueProvider.TargetObject}' is not valid for a LazyBinding. The LazyBinding target must be a UIElement.");
}
bindingTargetProperty = valueProvider.TargetProperty as DependencyProperty;
if (bindingTargetProperty == null)
{
throw new NotSupportedException($"The property '{valueProvider.TargetProperty}' is not valid for a LazyBinding. The LazyBinding target property must be a DependencyProperty.");
}
binding = new Binding
{
Path = Path,
Converter = Converter,
ConverterCulture = ConverterCulture,
ConverterParameter = ConverterParamter
};
if (ElementName != null)
{
binding.ElementName = ElementName;
}
if (RelativeSource != null)
{
binding.RelativeSource = RelativeSource;
}
if (Source != null)
{
binding.Source = Source;
}
binding.UpdateSourceTrigger = UpdateSourceTrigger;
binding.ValidatesOnDataErrors = ValidatesOnDataErrors;
binding.ValidatesOnExceptions = ValidatesOnExceptions;
binding.ValidatesOnNotifyDataErrors = ValidatesOnNotifyDataErrors;
return SetBinding();
}
return null;
}
public object SetBinding()
{
bindingTarget.IsVisibleChanged += UiElement_IsVisibleChanged;
updateBinding();
return bindingTarget.GetValue(bindingTargetProperty);
}
#endregion
#region Event Handlers
private void UiElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
updateBinding();
}
#endregion
#region Update Binding
private void updateBinding()
{
if (bindingTarget.IsVisible)
{
ConsolidateBinding();
}
else
{
ClearBinding();
}
}
private bool _isBind;
private void ConsolidateBinding()
{
if (_isBind)
{
return;
}
_isBind = true;
BindingOperations.SetBinding(bindingTarget, bindingTargetProperty, binding);
}
private void ClearBinding()
{
if (!_isBind)
{
return;
}
BindingOperations.ClearBinding(bindingTarget, bindingTargetProperty);
_isBind = false;
}
#endregion
}
}
To use:
<ItemsControl ItemsSource="{utils:LazyBinding Documents}"/>

Resources