Treeview should look like following:
Treeview Scheme
My Code looks like this:
<TreeView ItemsSource="{Binding Design.AllSystems}">
<HierarchicalDataTemplate DataType="{x:Type core:System}" ItemsSource="{Binding Windings}">
<StackPanel>
<TextBlock Text="{Binding SystemType.Detail1}"/>
<TextBlock Text="{Binding SystemType.Detail2}"/>
<TextBlock Text="{Binding SystemType.Windings}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView>
I try to achieve following:
There is a list of systems which contains some Details without Child elements and a Collection with Windings. I want to display those Details if the User jumps into a System. Additional there is a static Windings Entry which should display all Windings in the Windings-Collection.
I can't figure out how to do this. I am very thankful for any approach.
I think the easiest way to do this is to represent your information in some common type and build the structure in view models. You could use different types for the different levels if it helps you represent them or encapsulate their functionality, but this ultimately depends on what you will do with it.
For example, define a tree view entity:
public class TreeViewItemViewModel : ViewModelBase
{
#region Properties
public string Name { get; set; }
public ObservableCollection<TreeViewItemViewModel> Children { get; set; }
#endregion
}
You could extend this for specific roles if desired, and encapsulate the complexity of assembling the various child structures from multiple collections via these entries:
public class SystemTreeViewItemViewModel : TreeViewItemViewModel
{
// Other Properties
}
public class WindingTreeViewItemViewModel : TreeViewItemViewModel
{
// Other Properties
}
Then assemble a larger hierarchy of nodes:
public ObservableCollection<TreeViewItemViewModel> Systems { get; } = new ObservableCollection<TreeViewItemViewModel>
{
new SystemTreeViewItemViewModel
{
Name= "System 1",
Children = new ObservableCollection<TreeViewItemViewModel>
{
new TreeViewItemViewModel { Name = "SystemDetail1" },
new TreeViewItemViewModel { Name = "SystemDetail1" },
new TreeViewItemViewModel
{
Name = "Windings",
Children = new ObservableCollection<TreeViewItemViewModel>
{
new WindingTreeViewItemViewModel
{
Name = "Winding A",
Children = new ObservableCollection<TreeViewItemViewModel>
{
new TreeViewItemViewModel { Name = "Detail1" },
new TreeViewItemViewModel { Name = "Detail2" },
}
},
new WindingTreeViewItemViewModel
{
Name = "Winding A",
Children = new ObservableCollection<TreeViewItemViewModel>
{
new TreeViewItemViewModel { Name = "Detail1" },
new TreeViewItemViewModel { Name = "Detail2" },
}
}
}
}
}
},
new SystemTreeViewItemViewModel
{
Name= "System 2"
}
};
Then define HierarchialDataTemplate entries for each node type:
<TreeView ItemsSource="{Binding Systems}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:SystemTreeViewItemViewModel}" ItemsSource="{Binding Children}">
<Border Background="Red">
<TextBlock Text="{Binding Name}" />
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:WindingTreeViewItemViewModel}" ItemsSource="{Binding Children}">
<Border Background="LimeGreen">
<TextBlock Text="{Binding Name}" />
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:TreeViewItemViewModel}" ItemsSource="{Binding Children}">
<Border Background="Yellow">
<TextBlock Text="{Binding Name}" />
</Border>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Example on GitHub
Related
I face a problem using WPF.
Let's take those examples classes which reproduce what I'm trying to do :
public class Element
{
public string Name { get; set; }
public IList<Element> SubElements { get; set; } = new List<Element>();
public IList<Value> Values { get; set; } = new List<Value>();
}
public class Value
{
public string Name { get; set; }
}
Each Element instance can have (or not) its own list of Values.
For example i'd like to display the following root on my TreeView :
Element root = new Element() { Name = "Root" };
Element subElement1 = new Element() { Name = "SubElement1" };
Element subElement1_1 = new Element() { Name = "SubElement1_1" };
Value valueSubElement1_1 = new Value() { Name = "SubElement1_1_Value" };
subElement1_1.Values.Add(valueSubElement1_1);
subElement1.SubElements.Add(subElement1_1);
root.SubElements.Add(subElement1);
Element subElement2 = new Element() { Name = "SubElement2" };
Value valueSubElement2 = new Value() { Name = "SubElement2_Value" };
subElement2.Values.Add(valueSubElement2);
root.SubElements.Add(subElement2);
How could I do that ? I struggle to find a correct answer to this.
Here is the xaml i started with :
<TreeView x:Name="TreeView" Grid.Row="0" ItemsSource="{Binding TreeViewElements, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Element}" ItemsSource="{Binding SubElements}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Value}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
With TreeViewElements :
public IList<Element> TreeViewElements { get; set; } = new List<Element>();
to which I added my root object.
With that I can perfectly display all of my Element objects, but not the Values.
And I get why ; when I specified the ItemsSource I gave an item of type Element, so it'll never see the values inside.
So how could I do that ?
Thanks in advance for your answers and have a good day.
You can define as many templates as you like based on type but there can only be the one itemssource to a treeviewitem.
You could alter your Element or create a new ElementViewModel which had a list
public class Element
{
public string Name { get; set; }
public IList<object> Children { get; set; } = new List<object>();
}
You could then add an Element or Value to children.
Having said that.
Element has Name and SubElements, Value just has Name. It looks like you could just use an Element with an empty collection of SubElements instead of Value.
Just have
public class Element
{
public string Name { get; set; }
public IList<Element> SubElements { get; set; } = new List<Element>();
}
You would have to add another ItemsControl to the template to displays an additional collection on the same tree level.
Alternatively, if you want to display multiple types in the same tree then you would have to change your data structure with the goal to add all child items to the same source collection.
Solution 1
In order to be able hide the nested ToggleButton (that is used as the node's expander in this example) using a Trigger (in case the Element.Values source collection is empty) you could introduce a HasValues property:
Element.cs
public class Element
{
public string Name { get; set; }
public IList<Element> SubElements { get; set; } = new List<Element>();
public IList<Value> Values { get; set; } = new List<Value>();
public bool HasValues => this.Values.Count > 0;
}
The following modified TreeView example uses ToggleButton to toggle the Visibility of the nested ItemsControl.
Optionally, to match the look of the expander with the expander of the TreeView, you can extract the TreeView style using Visual Studio you will also get the ToggleButton style that the TreeView uses to style the tree node's expander. You can then set this Style to the below ToggleButton that toggles the visibility of the nested ItemsControl. In this case you would have to remove the ToggleButton.Content value that is currently set.
<TreeView>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Element}"
ItemsSource="{Binding SubElements}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander"
Grid.Row="0"
Grid.Column="0"
Content=">"
Margin="-24,0,0,0"
Visibility="Collapsed"
HorizontalAlignment="Left" />
<TextBlock x:Name="Header"
Grid.Row="0"
Grid.Column="1"
Text="{Binding Name}" />
<ItemsControl x:Name="ValueHost"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Visibility="Collapsed"
ItemsSource="{Binding Values}" />
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding HasValues}"
Value="True">
<Setter TargetName="Expander"
Property="Visibility"
Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsChecked}"
Value="True">
<Setter TargetName="ValueHost"
Property="Visibility"
Value="Visible" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Value}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
Solution 2
In your case you could introduce a common interface that declares the Name property. You can use this interface or the least common type (object) as the type of the container collection:
INode.cs
interface INode : INotifyPropertyChanged
{
string Name { get; set; }
}
Element.cs
// TODO::Implement INotifyPropertyChnaged
class Element : INode
{
public IList<INode> ChildNodes { get; set; }
public string Name { get; set; }
}
Value.cs
// TODO::Implement INotifyPropertyChnaged
class Element : INode
{
public string Name { get; set; }
}
Example
Element root = new Element() { Name = "Root" };
Element subElement1 = new Element() { Name = "SubElement1" };
Element subElement1_1 = new Element() { Name = "SubElement1_1" };
Value valueSubElement1_1 = new Value() { Name = "SubElement1_1_Value" };
subElement1_1.ChildNodes.Add(valueSubElement1_1); subElement1.ChildNodes.Add(subElement1_1);
root.ChildNodes.Add(subElement1);
Element subElement2 = new Element() { Name = "SubElement2" };
Value valueSubElement2 = new Value() { Name = "SubElement2_Value" };
subElement2.ChildNodes.Add(valueSubElement2);
root.ChildNodes.Add(subElement2);
<TreeView>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Element}"
ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Value}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I have 2 tasks.
Add a single usercontrol to a parent window.
Add a collection of a usercontrol to a parent window.
I have problem to fulfill task 2 in relation to the data binding and command binding.
if someone knows how to do task 2, please add some code.
This is my implementation for both tasks, in case someone want to fix it.. :
I have a usercontrol called "Book" that contains 3 textblocks and a button.
The userControl has dependecyProperty of my book model and for the button command.
Book.xaml
<UserControl x:Name="MyBookControl"
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Controls:BookControl}}, Path=TheBook}">
<Label Grid.Row="0">Title</Label>
<Label Grid.Row="1">Author</Label>
<Label Grid.Row="2">Description</Label>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Title}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Author}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Description}"/>
<Button Grid.Row="3" Command="{Binding
SomeCommand,ElementName=MyBookControl}" Content="Save" />
</Grid>
Book.xaml.cs
public partial class BookControl : UserControl
{
public BookControl()
{
InitializeComponent();
}
public BookModel TheBook
{
get { return (BookModel)GetValue(TheBookProperty); }
set { SetValue(TheBookProperty, value); }
}
public static DependencyProperty TheBookProperty = DependencyProperty.Register("TheBook", typeof(BookModel), typeof(BookControl));
public ICommand SomeCommand
{
get { return (ICommand)GetValue(SomeCommandProperty); }
set { SetValue(SomeCommandProperty, value); }
}
public static readonly DependencyProperty SomeCommandProperty =
DependencyProperty.Register("SomeCommand", typeof(ICommand), typeof(BookControl), new UIPropertyMetadata(null));
}
BookModel.cs
public class BookModel
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
}
In order to complete task 1 I created a window:
BookWindow
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
>
<StackPanel>
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
</StackPanel>
BookViewModel.cs
public BookModel Book { get; set; }
public MainViewModel()
{
Book = new BookModel{Title = "A Book", Author = "Some Author",
Description = "Its a really good book!"};
}
private ActionCommand _SaveCommand;
public ICommand SaveCommand
{
get
{
if (_SaveCommand == null)
{
_SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
return _SaveCommand;
}
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("save clicked");
}
protected virtual bool CanSaveCommand()
{
return true;
}
Great, Task 1 Completed
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!116&authkey=!AHiyrfEnBr2a-rM&v=3&ithint=photo%2cpng
Now, trying to complete task 2:
ContainerWindow:
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Container}"
>
<StackPanel>
<ItemsControl ItemsSource="{Binding Books}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
ContainerViewModel.cs :
private ObservableCollection<BookModel> books;
public ObservableCollection<BookModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookModel>();
}
return books;
}
}
public ContainerViewModel()
{
BookModel book1 = new BookModel { Title = "A Book 2", Author = "Some Author", Description = "Its a really good book!" };
BookModel book2 = new BookModel { Title = "A Book 3", Author = "Some Author", Description = "Its a really good book!" };
Books.Add(book1);
Books.Add(book2);
}
The Binding fail, the button "save" stops respoding.
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!121&authkey=!AKnyQk6Ge_9QHug&v=3&ithint=photo%2cpng
So, what is going on ? why binding fail, why the button "save" is not functioning ?
You're not setting your DependencyProperties in the list example.
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
Look at how you did it in your non-list version.
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
That being said, you don't need the DependencyProperties at all, the UserControl will inherit the DataContext for each 'Book' in the list of books as the ItemsControl creates them. You just need to not set the DataContext on the grid.
Then your button could just bind to the BookViewModel command property.
<Button Grid.Row="3" Command="{Binding SaveCommand}" Content="Save" />
If your concern is not knowing what is available for the inherited DataContext, you could do this to get design time support.
d:DataContext="{d:DesignInstance Type=local:BookViewModel,
IsDesignTimeCreatable=False}"
Just make sure that the following is defined somewhere in the file, it usually is by default.
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Update
So I missed the second issue, should have actually fired up Visual Studio. The issue is that your command is in the MainViewModel.cs. That said, our UserControl has inherited the DataContext of each Book object. The short of it is that the button is looking for the command inside of the Book object.
I'm going to assume that since you have a save command that you will be editing the Book object. So let's take this chance to go ahead and make a ViewModel. I'm going to move the save command to there, so that save is always available off of a BookViewModel. There could be good reasons to have the save command somewhere else, but for simplicity's sake, we'll put it in the ViewModel.
Also, I'm not sure if you have INotifyPropertyChanged implemented anywhere, as your MainViewModel and ContainerViewModel don't show that one is used. If you don't, I'd highly recommend you take a step back and look into an implementation or an MVVM framework for your ViewModels.
BookViewModel.cs
public class BookViewModel
{
private readonly BookModel book;
public BookViewModel(BookModel book)
{
this.book = book;
SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
public ICommand SaveCommand { get; private set; }
public string Title
{
get { return book.Title; }
set { book.Title = value; }
}
public string Author
{
get { return book.Author; }
set { book.Author = value; }
}
public string Description
{
get { return book.Description; }
set { book.Description = value; }
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("Save clicked for the book '" + Title + "'.");
}
protected virtual bool CanSaveCommand()
{
return true;
}
}
That is a very basic example of what you would probably want to do. I wanted to keep it simple to not take away from the example, you will probably want to at least do some null checking.
With the above, you shouldn't have to change your UserControl any, I had to add the row and column definitions, but I ended up with the following:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">Title</Label>
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding Title}" />
<Label Grid.Row="1" Grid.Column="0">Author</Label>
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding Author}" />
<Label Grid.Row="2" Grid.Column="0">Description</Label>
<TextBlock Grid.Row="2"
Grid.Column="1"
Text="{Binding Description}" />
<Button Grid.Row="3"
Grid.Column="0"
Command="{Binding SaveCommand}"
Content="Save" />
</Grid>
Hopefully you noticed that our BookViewModel's constructor accepts a book, so that means that we need to change our ContainerViewModel to house the proper collection and create them correctly.
public class ContainerViewModel
{
private ObservableCollection<BookViewModel> books;
public ContainerViewModel()
{
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 2",
Author = "Some Author",
Description = "Its a really good book!"
}));
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 3",
Author = "Some Author",
Description = "Its a really good book!"
}));
}
public ObservableCollection<BookViewModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookViewModel>();
}
return books;
}
}
}
All that and your ItemsControl can simply be as follows:
<ItemsControl ItemsSource="{Binding Path=Books}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyBookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Okay, I got this Tabcontrol containing a ListBox. Now my problem it that I would like to bind <TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/> to the selectedItem in the listbox and show the Detail property value.
Note that the TextBox is not part of the TabControl, but is in another Column.
I can't quite figure out, how to handle binding, when there a multiple ListBox'es, one in each TabControl Item.
My classes
public class TabViewModel
{
public string Name { get; set; }
public ObservableCollection<TabItemViewModel> Collection { get; set; }
}
public class TabItemViewModel
{
public string Title { get; set; }
public string Detail { get; set; }
}
public MainWindow()
var tabViewModels = new ObservableCollection<TabViewModel>();
tabViewModels.Add(new TabViewModel{Name = "Tab 1", Collection = new ObservableCollection<TabItemViewModel>{new TabItemViewModel{Detail = "Detail 1.1", Title = "Title 1.1"}, new TabItemViewModel{Detail = "Detail 2.2", Title = "Title 2.2"}}});
tabViewModels.Add(new TabViewModel { Name = "Tab 2", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 2.1", Title = "Title 2.1" }, new TabItemViewModel { Detail = "Detail 2.2", Title = "Title 2.2" } } });
tabViewModels.Add(new TabViewModel { Name = "Tab 3", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 3.1", Title = "Title 3.1" }, new TabItemViewModel { Detail = "Detail 3.2", Title = "Title 3.2" } } });
DataContext = tabViewModels;
MainWindow.xaml.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<TabControl ItemsSource="{Binding}" Grid.Column="0" SelectedIndex="0">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header">
<Setter.Value>
<Binding Path="Name"/>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<StackPanel Grid.Column="1">
<TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/>
</StackPanel>
</Grid>
EDIT
Temp Solution
Found a way to make it work, but I'm still looking for a pure Xaml solution.
Added a SelectionChange event
<ListBox ItemsSource="{Binding Collection}" SelectionChanged="ListBox_SelectionChanged">
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
DetailTextBox.DataContext = (TabItemViewModel) e.AddedItems[0];
}
How about this, I was surprised myself :-)
Make these changes to your Xaml.
<TabControl ItemsSource="{Binding}"
Grid.Column="0" SelectedIndex="0"
IsSynchronizedWithCurrentItem="True">
<ListBox ItemsSource="{Binding Collection}"
IsSynchronizedWithCurrentItem="True">
<TextBox x:Name="DetailTextBox"
Text="{Binding /Collection/Detail}"/>
The '/' binds to the currently selected item of a control's CollectionView.
So the binding above is drilling down through
The currently SelectedItem of the ObservableCollection set on the Data Context
The Collection property on that item
The currently SelectedItem of the Collection property (ObservableCollection)
The Detail property on that item.
In order for this to work we need to specify IsSynchronizedWithCurrentItem="True" to ensure the SelectedItem remains synchronized with the current item of each collection.
I have two comboboxes created as common controls. When the page is loaded, the programcbo lists the programs names; and the teamcbo list the team names. I want to ONLY display the related team names when any program name has been selected. In a word, I need to filter the second combobox by selecting the name from the first combobox.
Thanks in advance.
There are a variety of ways you could handle this.
If you have, for example, "Program" objects that contain collections of "Teams", you could do it like this, almost all in XAML:
<StackPanel>
<ComboBox x:Name="programCbo" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramName}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="teamCbo" ItemsSource="{Binding ElementName=programCbo, Path=SelectedItem.Teams}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TeamName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
You can see here that I've bound the first combobox to the datacontext, which is a list of Programs (we'll set that in the next bit). The second combobox is set to the selecteditem property of the first combobox, and then the Teams property on that. This way, when the selection changes on the first combobox, databinding kicks in and causes the itemssource on the second box to update.
In the code behind, I just build up the datasource. Obviously you'd have to get your data your own way:
public MainPage()
{
InitializeComponent();
DataContext = new List<Program>
{
new Program
{
ProgramName = "Program 1",
Teams = new List<Team>
{
new Team
{
TeamName = "Program 1 Team 1"
},
new Team
{
TeamName = "Program 1 Team 2"
}
}
},
new Program
{
ProgramName = "Program 2",
Teams = new List<Team>
{
new Team
{
TeamName = "Program 2 Team 1"
},
new Team
{
TeamName = "Program 2 Team 2"
}
}
}
};
}
If you don't have your data in a way that's accessible like this, you'll have to handle the Selection changed event on your combobox:
(XAML)
<StackPanel>
<ComboBox x:Name="programCbo" SelectionChanged="programCbo_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramName}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="teamCbo">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TeamName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Notice this time, we've set a handler for the SelectionChanged event.
(CodeBehind)
public MainPage()
{
InitializeComponent();
programCbo.ItemsSource = new List<Program>
{
new Program
{
ProgramName = "Program 1",
},
new Program
{
ProgramName = "Program 2",
}
};
}
private void programCbo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// get the sender
ComboBox cb = sender as ComboBox;
// get the selected program
Program selectedProgram = (cb.SelectedItem as Program);
// do some stuff to get the appropriate teams and set the other combobox's itemssource to it
teamCbo.ItemsSource = new List<Team>
{
new Team
{
TeamName = "My favorite team!"
}
};
}
And there you have it. Long winded, but hopefully thorough with examples :)
You can use a paged collection to filter or you can modify your observable collection which is bound to the second combobox when the value that's bound to the first combobox raises on prop change.
xaml:
...
xmlns:local="clr-namespace:SelectedProgram">
<UserControl.Resources>
<local:MainPageViewModel x:Key="vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource vm}}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="350" />
</Grid.RowDefinitions>
<sdk:Label Content="Select Team for the Program" Grid.Row="0" Margin="20,0,0,0"/>
<StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Top" Margin="20,0,0,0">
<ComboBox x:Name="programcbo" Width="100" Height="25"
ItemsSource="{Binding Path=Programs, Mode=OneWay}"
SelectedValue="{Binding Path=SelectedTeam, Mode=TwoWay}"
DisplayMemberPath="Key"
SelectedValuePath="Value" />
<ComboBox x:Name="teamcbo" Width="100" Height="25" Margin="20"
ItemsSource="{Binding Path=SelectedTeam, Mode=OneWay}"/>
</StackPanel>
</Grid>
In class like MainPageViewModel:
...
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Windows.Data;
namespace SelectedProgram
{
public class MainPageViewModel : ModelBase
{
public MainPageViewModel()
{
this.Programs = GetPrograms();
}
private string[] _selectedTeam;
public string[] SelectedTeam
{
get { return _selectedTeam; }
set
{
_selectedTeam = value;
FirePropertyChanged("SelectedTeam");
}
}
private ObservableCollection<KeyValuePair<string, string[]>> _programs;
public ObservableCollection<KeyValuePair<string, string[]>> Programs
{
get { return _programs; }
set
{
_programs = value;
FirePropertyChanged("Programs");
}
}
private ObservableCollection<KeyValuePair<string, string[]>> GetPrograms()
{
ObservableCollection<KeyValuePair<string, string[]>> pr = new ObservableCollection<KeyValuePair<string, string[]>>();
pr.Add(new KeyValuePair<string, string[]>("Janitors", new string[]{ "Schwarzkopf", "Ivanov", "Smith"}));
pr.Add(new KeyValuePair<string, string[]>("Violonists", new string[] { "Einstein", "Odinzovobulyznicof", "Onestone" }));
pr.Add(new KeyValuePair<string, string[]>("Programers", new string[] { "Petrov", "Skeet", "Stroustrup" })); return pr;
}
}
}
I have a window MainWindow.xaml and
private static Tutorial tutorial; there.
Also I have class Structure.cs where I describe child types
public class Tutorial
{
public string Name { get; set; }
public IList<Chapter> Chapters = new List<Chapter>();
}
public class Chapter
{
public string Name { get; set; }
public IList<Unit> Units = new List<Unit>();
}
public class Unit
{
public string Name { get; set; }
public IList<Frame> Frames = new List<Frame>();
...
}
I want to bind tutorial structure to treeview. How can I do this?
I tried this way.
<TreeView Grid.Row="2" x:Name="treeViewStruct" Margin="5,0,5,0" Background="LemonChiffon" BorderBrush="Bisque" BorderThickness="1" ScrollViewer.VerticalScrollBarVisibility="Auto" IsTextSearchEnabled="True" Cursor="Hand">
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type Structure:Chapter}"
ItemsSource = "{Binding Path=Units}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Structure:Unit}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It doesn't work.
Please, help! I'm a newbie in WPF. I need dynamic tree
so that when I add a chapter or a unit in the object tutorial, tree is updated.
And for this way of binding please throw the idea how can I get a collection item, when I selected some tree node.
This may help :
<HierarchicalDateTemplate DataType = "{x:Type local:Tutorial}"
ItemsSource="{Binding Chapters}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<HierarchicalDateTemplate DataType = "{x:Type local:Chapter}"
ItemsSource="{Binding Units}"
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<DateTemplate DataType = "{x:Type local:Unit}"
<TextBlock Text="{Binding Name}"/>
</DateTemplate>