im using WPF/MVVM to fool around with a treeview control
<TreeView HorizontalAlignment="Left"
Height="319"
VerticalAlignment="Top"
Width="517"
ItemsSource="{Binding Tree}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Button Width="100" Height="20" IsEnabled="{Binding IsEnabled}" Content="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And this is my viewmodel with the node class
public partial class MainWindow : Window
{
class TreeNode
{
public string Name { get; set; }
public bool IsEnabled { get; set; }
public List<TreeNode> Children { get; set; }
public TreeNode()
{
Children = new List<TreeNode>();
IsEnabled = true;
}
}
class ViewModel
{
public List<TreeNode> Tree { get; private set; }
public ViewModel()
{
Tree = new List<TreeNode>();
}
}
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel
{
Tree =
{
new TreeNode
{
Name = "Root 1",
IsEnabled = false,
Children = {
new TreeNode { Name = "Child 1" },
new TreeNode { Name = "Child 2" },
new TreeNode { Name = "Child 3",
Children =
{
new TreeNode { Name = "Child 3-1" },
new TreeNode { Name = "Child 3-2" },
new TreeNode { Name = "Child 3-3" },
}
},
}
}
}
};
DataContext = viewModel;
}
}
As you can see, i bind the property "IsEnabled" to the button, this is all good, but i actually want to bind the "IsEnabled" property to the actual root node element, not a object within the node.
What would i need to do to disable the entire root node based on this property value?
To do this, you actually need to affect the ItemContainerStyle for the TreeView. This sets the style for the ItemContainer (TreeViewItem) that is generated for each node of the Tree.
<TreeView HorizontalAlignment="Left" Height="319" VerticalAlignment="Top" Width="517" ItemsSource="{Binding Tree}">
<TreeView.ItemTemplate>
<!-- Leave this the same, except for the IsEnabled binding on the button -->
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Related
Let's say I have a questionnaire app, which consists of an ItemsControl with a list of controls each consisting of a Label and a ListBox. The items in each ListBox are checkboxes or radiobuttons or whatever.
My question is: When a checkbox is checked, how do I figure out which Question the checkbox applies to? Should I put a reference to the Question in the Tag property? If so, how would I do that?
The Tag binding code below doesn't work. It binds to the ListBoxItem. How do I bind it to the ItemsControl item?
MainWindow.xaml:
<Window x:Class="ListWithinListTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="ConditionCheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox IsChecked="{Binding IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Click="CheckBoxClicked"
Tag="{Binding RelativeSource={RelativeSource TemplatedParent}}"
>
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ItemsControl Name="QuizControl" ItemsSource="{Binding QuizQuestions}" ScrollViewer.CanContentScroll="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10 0 10 10" VerticalAlignment="Top">
<Label Content="{Binding Text}" />
<ListBox ItemsSource="{Binding Options}"
DisplayMemberPath="Text"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Style="{StaticResource ConditionCheckBoxListStyle}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace ListWithinListTest
{
public class Option
{
public string Text { get; set; }
public bool IsSelected { get; set; } = false;
}
public class Question
{
public string Text { get; set; }
public Option[] Options { get; set; }
}
public class ViewModel
{
public Question[] QuizQuestions { get; set; }
public ViewModel()
{
QuizQuestions = new Question[] {
new Question { Text = "How are you?", Options = new Option[] { new Option { Text = "Good" }, new Option { Text = "Fine" } } },
new Question { Text = "How's your dog?", Options = new Option[] { new Option { Text = "Sleepy" }, new Option { Text = "Hungry" } } },
};
}
}
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel = new ViewModel();
}
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
Question question = viewModel.QuizQuestions[???];
}
}
}
Ok, I messed with the Live Visual Tree window until, by process of elimination, I realized that the ListBox is what the Question is going to be bound to. I know now that's a big duh, but that's where I am with this. Then I used RelativeSource AncestorType to find it and the DataSource property to get the question:
MainWindow.xaml:
<CheckBox IsChecked="{Binding IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Click="CheckBoxClicked"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ListBox}}"
>
MainWindow.xaml.cs:
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
ListBox listBox = (ListBox)checkBox.Tag;
Question question = (Question)listBox.DataContext;
Debug.WriteLine(question.Text);
}
You have binded QuizQuestions to the QuizControl, you can get it back from the ItemsSource property.
var questions = (Question[]) QuizControl.ItemsSource;
EDIT
It looks like you got the answer yourself, just another way I would like to suggest to your original question:
Create one more property to your Option class
public class Option
{
public string Text { get; set; }
public bool IsSelected { get; set; } = false;
public int Index{ get; set; }
}
And then add Index to each of your question options.
QuizQuestions = new Question[] {
new Question { Text = "How are you?", Options = new Option[] { new Option { Text = "Good", Index = 0 }, new Option { Text = "Fine", Index = 0 } } },
new Question { Text = "How's your dog?", Options = new Option[] { new Option { Text = "Sleepy", Index = 1 }, new Option { Text = "Hungry", Index = 1 } } },
};
In your CheckBox event you can get the Option Index
private void CheckBoxClicked(object sender, RoutedEventArgs e)
{
var s = (CheckBox)sender;
var op = (Option)s.Tag;
Question question = viewModel.QuizQuestions[op.Index];
}
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
I have a parent ListBox which is made of two listboxes, and a child listbox, so 3 listboxes in total. The Item source for the first two are just two separate collections, these collections contain inner collection which then become an item source for the third listbox. If I select second item then the child listbox is populated again, if I go to the second parent listbox and chose something then the child listbox is populated. The problem is that if at this point I go back to the first listbox, and try to select an item that was previously selected, nothing happens, while im expecting it to populate the child listbox again.
Hopefully this makes sense, here is the XAML:
<Window x:Class="ExampleForStackOverFlow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="350" Height="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Parents" FontSize="20" FontWeight="Bold" Background="LightBlue"/>
<TextBlock Text="First Listbox" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Parents1}" SelectedItem="{Binding SelectedParent}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="Second Listbox" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Parents2}" SelectedItem="{Binding SelectedParent2}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="Children" FontSize="16" FontWeight="Bold" Background="LightBlue"/>
<ListBox ItemsSource="{Binding Children}" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
This is the ViewModel
using System.Collections.ObjectModel;
namespace ExampleForStackOverFlow
{
public class MainWindowViewModel : NotifyPropertyChanged
{
public MainWindowViewModel()
{
Parents1 = new ObservableCollection<Parent>();
Parents2 = new ObservableCollection<Parent>();
foreach (var parent in ParentCollection.GetParents())
{
if (parent.Name == "Parent 1" || parent.Name == "Parent 2")
{
Parents1.Add(parent);
}
else
{
Parents2.Add(parent);
}
}
Children = new ObservableCollection<Child>();
}
public ObservableCollection<Parent> Parents1 { get; set; }
public ObservableCollection<Parent> Parents2 { get; set; }
private ObservableCollection<Child> children;
public ObservableCollection<Child> Children
{
get { return children; }
set
{
children = value;
OnPropertyChanged("Children");
}
}
private Parent selectedParent;
public Parent SelectedParent
{
get { return selectedParent; }
set
{
selectedParent = value;
Children = SelectedParent.Children;
OnPropertyChanged("SelectedParent");
}
}
private Parent selectedParent2;
public Parent SelectedParent2
{
get { return selectedParent2; }
set
{
selectedParent2 = value;
Children = SelectedParent2.Children;
OnPropertyChanged("SelectedParent2");
}
}
}
public class Parent : NotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<Child> childres;
public ObservableCollection<Child> Children
{
get { return childres; }
set
{
childres = value;
OnPropertyChanged("Children");
}
}
}
public class Child : NotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
}
}
The value for selected did not change so it is not going to fire the set
If you set it to null then a re-select will be a new value
private Parent selectedParent;
public Parent SelectedParent
{
get { return selectedParent; }
set
{
selectedParent2 = null;
selectedParent = value;
Children = SelectedParent.Children;
OnPropertyChanged("SelectedParent");
OnPropertyChanged("SelectedParent2"); // you may not need this
}
}
private Parent selectedParent2;
public Parent SelectedParent2
{
get { return selectedParent2; }
set
{
selectedParent = null;
selectedParent2 = value;
Children = SelectedParent2.Children;
OnPropertyChanged("SelectedParent"); // you may not need this
OnPropertyChanged("SelectedParent2");
}
}
I'm just trying to find a way to control the expand / collapse of the TreeView nodes through the object they're bound to. The object has an IsExpanded property, and I want to use that to show the TreeView node itself expanded or collapsed based on that property.
Here's my code:
C#:
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
this.DataContext = new List<Parent>() { Base.GetParent("Parent 1"), Base.GetParent("Parent 2") };
}
}
public class Base
{
public string Name { get; set; }
public bool IsExpanded { get; set; }
public static Parent GetParent(string name)
{
Parent p = new Parent() { Name = name };
p.Children.Add(new Child() { Name = "Child 1", GrandChildren = new ObservableCollection<GrandChild>() { new GrandChild() { Name = "Grandchild 1" } } });
p.Children.Add(new Child() { Name = "Child 2", GrandChildren = new ObservableCollection<GrandChild>() { new GrandChild() { Name = "Grandchild 1" } } });
p.Children.Add(new Child() { Name = "Child 3", GrandChildren = new ObservableCollection<GrandChild>() { new GrandChild() { Name = "Grandchild 1" } } });
return p;
}
}
public class Parent : Base
{
public ObservableCollection<Child> Children { get; set; }
public Parent()
{
this.Children = new ObservableCollection<Child>();
}
}
public class Child : Base
{
public ObservableCollection<GrandChild> GrandChildren { get; set; }
public Child()
{
this.GrandChildren = new ObservableCollection<GrandChild>();
}
}
public class GrandChild : Base
{
}
XAML:
<Window x:Class="HeterogeneousExperimentExplorer.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HeterogeneousTree"
Title="Window2" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Parent}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Parent}" ItemsSource="{Binding GrandChildren}">
<TextBlock Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding}" />
</Grid>
</Window>
Came up with solution:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded}"/>
</Style>
So the style gets the object bound to the TreeViewItem and looks at its IsNodeExpanded attribute and it assigns that value to the TreeViewItem.IsExpanded property. If you add Mode=TwoWay, they'll notify each other (TreeViewItem will tell the object when it has been expanded).
I am working on wpf mvvm pattern.
I have a user control in which i am loading a list of checkboxes in DataGridCheckBoxColumn and binding it to IsSelected property from my viewmodel.
Tha xaml code is like this:
<DataGrid Width="150" Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSelected,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,IsAsync=True}" Width="50">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" FontWeight="Bold" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=UserControl },Mode=TwoWay}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="85" Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
My HomeViewModel is like this:
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private Control _someUserControl;
public Control CCS01
{
get { return _someUserControl; }
set { _someUserControl = value; }
}
private UserControl _content;
internal void SetNewContent(UserControl _content)
{
ContentWindow = _content;
}
public UserControl ContentWindow
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("ContentWindow");
}
}
For my 1st checkbox (CCS01), I have created a view(CCS01.xaml) which contains a few labels and textboxes in grid format.And its corresponding ViewModel is below:
public class CCS01ViewModel: BaseNotifyPropertyChanged
{
HomeViewModel _homeViewModel;
public ICommand OpenUsersCommand { get; private set; }
public CCS01ViewModel(HomeViewModel mainModel)
{
this._homeViewModel = mainModel;
//this._model = model;
OpenUsersCommand = new RelayCommand(OpenUsers, CanOpenUsers);
}
private void OpenUsers(object _param)
{
//UsersPanelViewModel upmodel = new UsersPanelViewModel(_mainModel, _model);
//UsersPanel up = new UsersPanel();
//up.DataContext = upmodel;
//_mainModel.SetNewContent(up);
}
private bool CanOpenUsers(object _param)
{
return true;
}
}
I want to load the selected checkbox view in the main UserControl(ExecutionDetails.xaml) . Currently, I am loading it with the first Checkbox view in it by default like this:
<StackPanel>
<Grid Name="HostGrid">
<ContentControl Content="{Binding ContentWindow}"/>
</Grid>
</StackPanel>
Please suggest me how to bind the view with respective checkboxes user controls dynamically based on the checkbox selection.
The code behind of this(ExecutionDetails.xaml.cs) is like this:
public partial class ExecutionDetails : UserControl
{
public ExecutionDetails()
{
InitializeComponent();
HomeViewModel viewmodel = new HomeViewModel();
CCS01ViewModel ccViewModel = new CCS01ViewModel(viewmodel);
CCS01 obj = new CCS01();
obj.DataContext = ccViewModel;
viewmodel.ContentWindow = obj;
this.DataContext = viewmodel;
}
}