TreeView with RadioButtons - c#

I need to create TreeView with dynamicly node creation in my WPF project. There is one condition for TreeView Nodes. All leafs must contains Radiobuttons with work logic same as group of RadioButtons.
I have this XAML code, that describes treeview data template:
<HierarchicalDataTemplate x:Key="sko_ver_hdt">
<RadioButton Margin="0,0,10,0" Content="{Binding Version}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="sko_hdt"
ItemTemplate="{StaticResource sko_ver_hdt}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
I've tried to use CollectionViewSource to group nodes of TreeView like this:
<CollectionViewSource x:Key="cvs" Source="{Binding SKOs}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Version"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
but nothing happens.
I have a model that describes treeview data:
internal class SKOVM
{
public SKOVM(DBCommunication.SKO sko)
{
Name = sko.Name;
Version = sko.VersionCode;
Id = sko.Id;
Children = new ObservableCollection<SKOVM>(sko.SKO1.Select(x => new SKOVM(x)));
sko.SKOSystemInformationReference.Load();
if (sko.SKOSystemInformation != null)
Version = String.Format("{0} / {1}", Version, sko.SKOSystemInformation.Designer);
}
public long Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public ObservableCollection<SKOVM> Children { get; set; }
}
Any ideas how i can implement grouped radiobuttons behaviour logic to my treeview leaf nodes?
Thanks in advance.

Have you tried using the RadioButton.GroupName property?:
<HierarchicalDataTemplate x:Key="sko_ver_hdt">
<RadioButton Content="{Binding Version}" GroupName="{Binding GroupName}" />
</HierarchicalDataTemplate>
This property Gets or sets the name that specifies which RadioButton controls are mutually exclusive. So you'll need a property in the child item class where the child items of each group will have the same value. In this way, you can data bind that value to the GroupName property and the RadioButton of each child item should work in their individual groups.

Related

uwp: how to bind data inside DataTemplate outside of x:DataType?

i have MyPage.xaml and MyPage.xaml.cs files.
Into .xaml file i written a data template like this:
<DataTemplate x:Key="MyDataTemplate" x:DataType="local:MyClass">
...
<TextBlock Text="{x:Bind name}" HorizontalAlignment="Left" TextWrapping="Wrap"/>
...
</DataTemplate>
I can bind name attribute of MyClass correctly.
Now i need to bind an attribute of .xaml.cs file but DataTemplate show me only MyClass data. How can i bind data from .xaml.cs page? Outside DataTemplate (but in the same xaml file) i can see any attribute of .xaml.cs file.
I need to bind a List of objects to a combobox like this:
<ComboBox ItemsSource="{x:Bind myList}" HorizontalAlignment="Left"></ComboBox>
but myList is an .xaml.cs attribute.
I want to view a string name attribute of objects of the list.
Thank you for your help
uwp: how to bind data inside DataTemplate outside of x:DataType?
For your recrement, we suggest you use Binding to replace x:Bind, You could use Binding to access current root DataContext with ElementName.
For example
<Grid x:Name="GridRoot">
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<ComboBox ItemsSource="{Binding ElementName=GridRoot, Path=DataContext.Options}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public List<string> Options { get; set; } = new List<string>() {"One","Two","Three" };
public List<Item> Items { get; set; } = new List<Item>()
{
new Item { Name = "HH" },
new Item { Name = "ZZ" },
new Item { Name = "MM" }
};
}
public class Item
{
public string Name { get; set; }
}

WPF recursive treeview with MVVM pattern

i'm new to WPF and MVVM pattern. I'm trying to bind recursively a Treeview to ObservableCollections.
I have searched so many times on this site, but I found no answers to my problem.
Here it is my Model class:
public class CategoryCounter
{
public int ID { get; set; }
public string Supplier { get; set; }
public string Name { get; set; }
public Nullable<int> Parent { get; set; }
public ObservableCollection<CategoryCounter> Children => new ObservableCollection<CategoryCounter>(/*some linq code here*/);
And the ViewModel class:
public class CategoriesViewModel : BaseViewModel
{
private string supplier;
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
if (value != categories)
{
categories = value;
}
}
}
public void SetSupplier(string supplier)
{
this.supplier = supplier;
Categories = new ObservableCollection<CategoryCounter>(CategoryContatori.GetRootBySupplier(supplier));
NotifyPropertyChanged();
}
}
Now, when i call "SetSupplier()" the collection is filled and it is all ok, but the view does not show me anything.
This is the XAML code:
<StackPanel>
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dbModel:CategoryCounter}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
How can I bind the children items even if they are the same object? Is this the problem?
Thank you for your patience.
Try setting the template directly
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Resources generally need to be in the parent of the item that needs to access them.
EDIT: Although having just done a quick search this may not be the case for TreeViews, so it's likely that INPC is your problem as already noted in the comments.
Raise the PropertyChanged in the setter of your Categories property:
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(nameof(Categories));
}
}
Also make sure that the Categories property and the Children property return materialized collections of CategoryCounter objects.

Populate a Tree View with a Custom List Object

I have a custom object that consists of 2 properties. The first is a string that i wish to use as a summary or header in the tree view. The second is a list of a custom type that contains objects that are to be included under each header. The objects contain things such as name, id, area, etc.. Ill most likely default to the name property of those list objects. How can I push this into a tree view.
Concatenated Model
public class WVWellModel : Notifier
{
private string _API;
public string API
{
get
{
return this._API;
}
set
{
this._API = value; OnPropertyChanged("API");
}
}
private string _WellName;
public string WellName
{
get
{
return this._WellName;
}
set
{
this._WellName = value; OnPropertyChanged("WellName");
}
}
private string _Division;
public string Division
{
get
{
return this._Division;
}
set
{
this._Division = value; OnPropertyChanged("Dvision");
}
}
private string _Area;
public string Area
{
get
{
return this._Area;
}
set
{
this._Area = value; OnPropertyChanged("Area");
}
}
private string _FieldOffice;
public string FieldOffice
{
get
{
return this._FieldOffice;
}
set
{
this._FieldOffice = value; OnPropertyChanged("FieldOffice");
}
}...............
** Model that will be put in a list to be injected into tree view**
public class groupingModel : Notifier
{
private string _Header;
public string Header
{
get { return _Header; }
set { _Header = value; OnPropertyChanged("Header"); }
}
private List<WVWellModel> _Wells;
public List<WVWellModel> Wells
{
get { return _Wells; }
set { _Wells = value; OnPropertyChanged("Wells"); }
}
}
List of Custom Type to be injected into tree view
List treeViewList = someMethod();
In summary, I would like to bind my tree view to a custom list object.List<groupingModel> The object in those lists have two properties, a string header that is to be used to group the objects in the tree view, and a second property that contains a list of custom objects "WVWellModel".
EDIT TO XAML to Allow Selection of all items in group
I've attempted to go ahead and make the group selectable with he goal that if the group is selected all children are selected underneath. Ive successfully bound it to a property inside of the group called "IsChecked". it defaults to false and works successfully. The problem is i am unable to capture the change in value and thus cannot run any logic to select its children.
<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="wellModel:WellGroupModel" ItemsSource="{Binding Wells}">
**<CheckBox Content="{Binding Header}" IsChecked="{Binding IsChecked}"/>**
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type wellModel:WellModel}">
<CheckBox Content="{Binding WellName}" IsChecked="{Binding IsSelected}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The TreeView control uses HierarchicalDataTemplate to control how items are displayed and how their children are populated. If your item class has children of a different type, it can specify its own child ItemTemplate, and so on recursively.
I've also added a minimal top-level viewmodel which owns a collection of GroupingModel. I'm using conventional C# naming conventions: Classes and properties start with a capital letter, private fields start with an underscore and a lower-case letter. It seems silly but when everybody uses the same convention, you always know what you're looking at.
Finally, I used ObservableCollection<T> rather than List<T>. If you bind an ObservableCollection to a control, then you can add/remove items in the collection and the control will automatically be notified and update itself without any additional work on your part.
XAML
<TreeView
ItemsSource="{Binding Groups}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemTemplate>
<!-- This can be DataTemplate if no child collection is specified -->
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<TextBlock Text="{Binding WellName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Alternatively, if you have heterogeneous collections of objects, you can create implicit templates as resources and let them be applied by type rather than by hierarchy. In your particular case, this will produce identical results, because you have a strict item hierarchy.
<TreeView
ItemsSource="{Binding Groups}"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<TextBlock Text="{Binding WellName}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
C#
public class ViewModel : Notifier
{
public ViewModel()
{
Groups = new ObservableCollection<GroupingModel>
{
new GroupingModel {
Header = "First Group",
Wells = new List<WVWellModel> {
new WVWellModel() { WellName = "First Well" },
new WVWellModel() { WellName = "Second Well" },
new WVWellModel() { WellName = "Third Well" },
}
},
new GroupingModel {
Header = "Second Group",
Wells = new List<WVWellModel> {
new WVWellModel() { WellName = "Third Well" },
new WVWellModel() { WellName = "Fourth Well" },
new WVWellModel() { WellName = "Fifth Well" },
}
}
};
}
#region Groups Property
private ObservableCollection<GroupingModel> _groups = new ObservableCollection<GroupingModel>();
public ObservableCollection<GroupingModel> Groups
{
get { return _groups; }
set
{
if (value != _groups)
{
_groups = value;
OnPropertyChanged(nameof(Groups));
}
}
}
#endregion Groups Property
}
Update
Let's make the WVWellModel items checkable. First, we'll give them a boolean property that we'll bind to the checkbox's IsChecked property:
public class WVWellModel : Notifier
{
private bool _isSelected;
public bool IsSelected
{
get
{
return this._isSelected;
}
set
{
this._isSelected = value; OnPropertyChanged();
}
}
And then we'll change the content in the WVWellModel DataTemplate from a TextBlock to a CheckBox:
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<CheckBox
Content="{Binding WellName}"
IsChecked="{Binding IsSelected}"
/>
</DataTemplate>
You can put any valid XAML UI in a template as long as there's a single root element.
<TreeView
Width="300"
Height="200"
ItemsSource="{Binding Groups}"
Grid.IsSharedSizeScope="True"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="CheckBoxColumn" />
<ColumnDefinition Width="Auto" SharedSizeGroup="APIColumn" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
Content="{Binding WellName}"
IsChecked="{Binding IsSelected}"
/>
<TextBlock
Grid.Column="1"
Margin="12,0,0,0"
Text="{Binding API}"
HorizontalAlignment="Right"
/>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>

How bind an object to treeview from Xaml

I am trying to bind an object to the treeviewcontrol WPF by XAML, I am getting the treview as empty. When i am doing that by treeview.items.add(GetNode()) then its working.
I am using MVVM Framework(caliburn.Micro) I wanted to do it in Xaml, how do I assign Item source property in xaml? I tried with creating a property of Node class and calling the Method GetNode() with in the property, and assigned that property as itemssource of the treeview and changed the List to Observable collection. Still issue is same.
Working Xaml when doing treeview.items.Add(GetNode()) which returns a Node and and i as assigning Nodes collection to Hireachial Template.
<TreeView Name="treeview2"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}" Width="300">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<DockPanel LastChildFill="True">
<TextBlock Padding="15,0,30,0" Text="{Binding Path=numitems}" TextAlignment="Right"
DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Text}" DockPanel.Dock="Left" TextAlignment="Left" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Server Side Code:
this.treeview2.Items.Add(GetNode());
GetNode recursively build a list of type Node.
public class Node
{
public string Text { get; set; }
public List<Node> Nodes { get; set; }
public ObservableCollection<Node> Nodes{get;set;} // with list and observable collection same results
public int numitems { get; set; }
}
In addition to the HierarchicalDataTemplate, which seems just fine, add a binding to the ItemsSource property of your TreeView:
public class ViewModel
{
private List<Node> _rootNodes;
public List<Node> RootNodes
{
get
{
return _rootNodes;
}
set
{
_rootNodes = value;
NotifyPropertyChange(() => RootNodes);
}
}
public ViewModel()
{
RootNodes = new List<Node>{new Node(){Text = "This is a Root Node}",
new Node(){Text = "This is the Second Root Node"}};
}
And in XAML:
<TreeView ItemsSource="{Binding RootNodes}"
.... />
Edit: Remove the call that does this.Treeview.... you don't need that. Try to keep to the minimum the amount of code that references UI Elements. You can do everything with bindings and have no need to manipulate UI Elements in code.

Dynamically grouping/nesting flat data in a TreeView

I would like to take a flat list of objects and present them in a TreeView using custom groups.
public enum DocumentType { Current, Inactive, Transition, Checkpack, TechLog, Delivery }
public enum Status { Approved, Rejected, Pending }
public class Document
{
public string Name { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public DocumentType Type { get; set; }
public Status Status { get; set; }
}
For example... The user might want to see this list, with the top level group being "Status" and the second level being "Name". This all needs to be configurable from the UI, and I'm struggling to find the best way to achieve it.
I've had a brief look at the CollectionViewSource object, but couldn't find a good way to get it to dynamically build a TreeView.
My gut feeling is that i'll need to do some clever templating in XAML - this is as far as i've got...
<Window.Resources>
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="GroupTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource DocumentTemplate}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Documents.View.Groups}"
ItemTemplate="{StaticResource GroupTemplate}"/>
</Grid>
public CollectionViewSource Documents
{
get
{
var docs = new CollectionViewSource();
docs.Source = DocumentFactory.Documents;
docs.GroupDescriptions.Add(new PropertyGroupDescription("CreatedBy"));
return docs;
}
}
Of course this only displays the Top-level group ("CreatedBy").
After reading a question below, I managed to come up with a better question...
My question: Is it possible to have a generic HierarchicalDataTemplate for a TreeView that displays custom groups applied to a CollectionViewSource.
Honestly this should be marked as a bug in WPF. I too tried and found that Documents.View.Groups throws binding error on View property being null.
Also
<TextBlock Text="{Binding Path=Name}" />
is correct in the GroupTemplate but not in the DocumentTemplate. Note that Groups are of special type GroupItem where Name is one such property that holds the value on which grouping has taken place.
On the other hand in DocumentTemplate, we should refer the property that we need to display on the leaf nodes items e.g. in my example I used Employee.FirstName (I grouped on Gender).
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding FirstName}" />
</DockPanel>
</DataTemplate>
Now for binding to take effect I had to introduce a converter which simply returns Groups.
public class GroupsConverter : IValueConverter
{
public object Convert(object value, ...)
{
return ((CollectionViewSource)value).View.Groups;
}
....
}
And tree view binding was changed this way...
<TreeView x:Name="treeView"
ItemsSource="{Binding Path=Documents,
Converter={StaticResource GroupsConverter}}"
ItemTemplate="{StaticResource GroupTemplate}" />
Then this worked for me.
Does this help you?

Categories

Resources