How to bind TreeView to ViewModel? - c#

I have a view model as follows:
public class SolutionViewModel : TreeViewItemViewModel {
public ObservableCollection<TreeViewItemViewModel> Children {
get { return mChildItems; }
}
public bool IsExpanded {
get { return mIsExpanded; }
set {
if (value != mIsExpanded) {
mIsExpanded = value;
OnPropertyChanged("IsExpanded");
}
if (mIsExpanded && mParentItem != null)
mParentItem.IsExpanded = true;
if (this.HasDummyChild) {
Children.Remove(EmptyItem);
LoadChildren();
}
}
}
public bool IsSelected {
get { return mIsSelected; }
set {
if (value != mIsSelected) {
mIsSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
protected override void LoadChildren() {
var subFolders = default(ReadOnlyCollection<Folder>);
if (!GetSubFolders(mSolution.Folder.Name, out subFolders)) {
subFolders = new ReadOnlyCollection<Folder>(new List<Folder>());
}
foreach (var folder in subFolders) {
Children.Add(new SolutionItemViewModel(this, folder));
}
}
public string SolutionName {
get { return mSolution.Name; }
}
}
The .Xaml for the TreeView is as follows:
<TreeView Name="SolutionTree" ItemsSource="{Binding SolutionViewModel}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localmodels:SolutionViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="..\Resources\Folder_25x25.png" />
<TextBlock Text="{Binding SolutionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
After a file is selected from SolutionExplorer:
public void SetBindingContext(SolutionViewModel SolutionViewModel) {
SolutionTree.DataContext = SolutionViewModel;
}
This is a lazy loading model so that when the item is expanded, the children are loaded.
The problem is that I am not even getting the Solution Name as the top level node.
Update:
I verified that the model has SolutionName assigned:
In addition, per comment from #elgonzo, I edited the .Xaml to reflect a change to ItemsSource binding:
<TreeView Name="SolutionTree" ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
Update 2
Code to assign the TreeView data context is executed when an event handler is raised after selecting a file from the OpenFileDialog:
private void OnOpenFile(string FilePath) {
mSolutionManager = SolutionManager.Load(FilePath);
mSolutionViewModel = new SolutionViewModel(mSolutionManager.Solution);
mMainWindow.SolutionExplorer.SetBindingContext(mSolutionViewModel);
mSolutionViewModel.Refresh();
}
When I step into the Refresh() method:
public void Refresh() {
OnPropertyChanged("SolutionName");
}
...I find that the PropertyChangedEventHandler has no subscribers.

Your binding to ItemsSource is wrong :
<TreeView Name="SolutionTree" ItemsSource="{Binding SolutionViewModel}">
ItemSource needs to be an IEnumerable.

Related

Expand Databound Treeview Item WPF

how can I expand my bound item in treeview?
My item's class:
public class TreeViewItemBase : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if (value != this.isSelected)
{
this.isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
if (value != this.isExpanded)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class FileModelViewTreeView : TreeViewItemBase
{
public int Level
{
get;
set;
}
public string Name
{
get;
set;
}
public string Path
{
get;
set;
}
public List<FileModelViewTreeView> SubItems
{
get;
set;
}
}
This code doesn't works by me:
((FileModelViewTreeView)toSelectIndex).IsExpanded = true;
((FileModelViewTreeView)toSelectIndex).IsSelected = true;
My treeview xaml code:
<TreeView SelectedItemChanged="FilesTreeView_SelectedItemChanged" x:Name="FilesTreeView" Margin="0,68,0,0" HorizontalAlignment="Left" Width="200" Background="Transparent" BorderBrush="#4c607a" BorderThickness="2">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FileModelViewTreeView}" ItemsSource="{Binding SubItems}">
<StackPanel Orientation="Horizontal" Margin="2">
<Image Source="pack://application:,,,/UI/Images/dir.png"
Width="21"
Height="21"
SnapsToDevicePixels="True"/>
<TextBlock Padding="5" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
All solutions which I found on other forums didn't worked for me :(
There are no exception at runtime and compiling, it just doesn't works. Maybe anyone already had that problem, please help me.
The cleanest way to do this is using a style.
<Style x:Key="MyTreeViewItemContainerStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
...
</style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="ItemContainerStyle" Value="{StaticResource MyTreeViewItemContainerStyle}" />
</Style>
Check out my blog post for my take on how to handle WPF TreeViews in a MVVM context.

WPF ListView Header Checkbox and MVVM Command

I have a listview in my WPF application and the first column is a Checkbox. This checkbox is bound to the IsSelected property of my model and the event propogation happens correctly.
I also have a Checkbox in the same column's header and want to implement a 'Select All' feature where it checks all the listview items.
I'm using pattern MVVM.
The Event doesn't fire!
Can someone explain what I am doing wrong here..
The relevant code portions are mentioned below..
XAML:
<ListView Grid.Row="0"
ItemsSource="{Binding Path=WorkOrders}"
Margin="5,10,5,5"
Name="WorkOrders"
SelectionMode="Multiple"
FontSize="13"
Background="AliceBlue"
BorderBrush="AliceBlue">
<!--Style of items-->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<!--Properties-->
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Control.VerticalContentAlignment" Value="Center" />
<!--Trigger-->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView >
<GridViewColumn CellTemplate="{StaticResource CheckBoxDataTemplate}" Width="80" >
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Command="{Binding Path=SelectAllCommand}" />
</DataTemplate>
</GridViewColumn.HeaderTemplate>
</GridViewColumn>
<GridViewColumn Header="WorkOrder" CellTemplate="{StaticResource DetailIdenTemplate}" Width="300"/>
</GridView>
</ListView.View>
</ListView>
Model:
public class WorkOrder
{
public int CD_WORK_ORDER { get; set; }
public string ID_WORK_ORDER { get; set; }
public bool IsSelected { get; set; }
}
ViewModel:
public class LockWorkOrderSelectionViewModel : ViewModelBase
{
RelayCommand _selectAllCommand;
public ICommand SelectAllCommand
{
get
{
if (_selectAllCommand == null)
{
_selectAllCommand = new RelayCommand(
param => SelectAllElement(),
param => CanSelectAll);
}
//RaiseEvent(new RoutedEventArgs(SearchEvent));
return _selectAllCommand;
}
}
private bool _selectedAllElement;
public bool SelectAllElement()
{
foreach (var item in WorkOrders)
{
item.IsSelected = true;
}
return true;
}
public bool CanSelectAll
{
get { return true; }
}
public List<string> WorkOrdersList
{
get { return _workOrdersList; }
}
private ObservableCollection<WorkOrder> _workOrders = new ObservableCollection<WorkOrder>();
public ObservableCollection<WorkOrder> WorkOrders
{
get
{
int progr = 1;
foreach (var item in WorkOrdersList)
{
if (_workOrders.FirstOrDefault(i => i.ID_WORK_ORDER == item) == null)
{
_workOrders.Add(new WorkOrder { CD_WORK_ORDER = progr, ID_WORK_ORDER = item, IsSelected = false });
progr++;
}
}
return _workOrders;
}
}
}
<CheckBox IsChecked="{Binding DataContext.SelectAll, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
Works for me.

WPF update TreeView when source updates for a tree model

I implemented a tree model like this:
public class Node
{
public NodeValue Item { get; set; }
public Node Parent { get; set; }
public List<Node> Children { get; set; }
}
And I displayed this nodes in a WPF TreeView.
<TreeView Name="MainTreeview" HorizontalAlignment="Left" Height="auto" VerticalAlignment="Top" Width="200" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}" SelectedItemChanged="MainTreeview_SelectedItemChanged" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local2:Node}" ItemsSource="{Binding Path=Children, Mode=OneWay}">
<TreeViewItem Header="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
My problem is that I cannot update view after I update the model. I don't understand from other articles how to update the UI, because they are too complex.
Now, I know that are 2 possible ways:
Using dependency properties: I understood how they work but I don't know how to apply them to my model.
Using Event triggers but I also don't know how to apply them to my problem.
Please help me!
Simply have your node implement INotifyPropertyChanged and use ObservableCollections instead of non-obserable Lists.
Partial implementation to get you on your way...
public class Node : INotifyPropertyChanged
{
private Node _parent;
public Node Parent
{
get
{
return this._parent;
}
set
{
if (value != this._parent)
{
this._parent= value;
NotifyPropertyChanged();
}
}
}
public ObservableCollection<Node> Children { get; } = new ObservableCollection<Node>()
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?(this, new PropertyChangedEventArgs(propertyName));
}
}

unable to get, set selectedItem from the listbox containing radiobuttons being generated dynamically in wpf MVVM

Am trying to populate listbox with dynamic radiobuttons which are being customized to togglebuttons. I could populate listbox items with radiobuttons as said above. However, once we select any of the radiobuttons am unable to set the selected item from list of radiobuttons in my viewmodel object while debugging.
The following is the xaml code in my resource directory
<Style x:Key="ScreensList" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
VerticalAlignment="Center" GroupName="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<StackPanel Width="80" Height="60" Orientation="Vertical" HorizontalAlignment="Left" Margin="10,10,20,10">
<Image Source="Default.png" Height="40"></Image>
<TextBlock Text="{Binding Path=ScreenNumber}" FontSize="11"></TextBlock>
</StackPanel>
</ToggleButton>
</StackPanel>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
the below is my xaml code in xaml page
<ListBox x:Name="ScreensList" ItemsSource="{Binding ScreenCollection}"
SelectedItem="{Binding Path=ScreenManager}"
Style="{StaticResource ScreensList}" Width="365">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel FlowDirection="LeftToRight" Orientation="Horizontal" >
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
the following is the viewmodel.cs
public ObservableCollection<ScreensManager> ScreenCollection { get; set; }
private ScreensManager _screenManager;
public ScreensManager ScreenManager
{
get { return _screenManager; }
set
{
if (_screenManager != value)
{
if (_screenManager != null)
{
_screenManager = value;
}
}
}
}
private void AddScreens()
{
int screenCount = Screen.AllScreens.Length;
if (ScreenCollection == null)
ScreenCollection = new ObservableCollection<ScreensManager>();
for (int screenCounter = 1; screenCounter <= screenCount; screenCounter++)
{
if (screenCounter == 1)
{
_screenManager = new ScreensManager();
_screenManager.ScreenNumber = screenCounter;
ScreenCollection.Add(_screenManager);
}
}
}
the following is the code in my ScreenManager.cs model class file
public ScreensManager()
{
}
private int _screenNumber;
public int ScreenNumber
{
get { return _screenNumber; }
set
{
_screenNumber = value;
OnPropertyChanged("ScreenNumber");
}
}
private bool _selectedScreen;
public bool SelectedScreen
{
get { return _selectedScreen; }
set
{
if (_selectedScreen = value)
{
_selectedScreen = value;
if (_selectedScreen != value)
{
OnPropertyChanged("SelectedScreen");
}
}
}
}
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
Am unable to find where I am actually going wrong as completely new to mvvm, someone please help me resolve my issue..Thanks in advance.
Your SelectedScreen is of type bool, and should be of type ScreensManager
Your ListBox.ItemsSource is bound to an ObservableCollection<ScreensManager>, meaning your ListBox contains a collection of ScreensManager objects, however your SelectedItem is of type bool. A bool object is never equal to a ScreensManager object, so WPF doesn't select anything since the SelectedItem is not found in the ItemsSource.
Change the SelectedScreen type to be ScreensManager instead of bool, and be sure it is equal to an item that exists in the ScreenCollection. WPF compares objects by reference, not value, so
ScreenManager.SelectedScreen = ScreenCollection.FirstOrDefault(); // Works
ScreenManager.SelectedScreen = new ScreensManager() { ... }; // Won't work
Make sure the binding is in two-way mode:
SelectedItem="{Binding Path=ScreenManager.SelectedScreen, Mode=TwoWay}"

How to bind the property to the selected node in the Treeview in WPF

How to bind the custom property to the selected node's IsSelected property in MVVM,
I have loaded the nodes at runtime into the treeview. I am using MVVM
Here's a small example:
public abstract class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyViewModel : ViewModel
{
public ObservableCollection<Item> Items
{
get
{
return new ObservableCollection<Item>()
{
new Item() {DisplayValue = "Item1", IsSelected = false, Sample = "Sample: I am Item1"},
new Item() {DisplayValue = "Item2", IsSelected = true, Sample = "Sample: I am Item2"},
new Item() {DisplayValue = "Item3", IsSelected = false, Sample = "Sample: I am Item3"}
};
}
}
}
public class Item : ViewModel
{
public string DisplayValue { get; set; }
private bool _isSelected = false;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
private string _sample;
public string Sample
{
get
{
return _sample;
}
set
{
_sample = value;
OnPropertyChanged("Sample");
}
}
}
XAML:
<Window x:Class="WpfApplication93.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication93"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.DataContext>
<local:MyViewModel></local:MyViewModel>
</Grid.DataContext>
<StackPanel>
<TreeView x:Name="myTreeView" ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<TextBlock Text="{Binding DisplayValue}"></TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<!-- if you want to customize the appearance of a selected element do it here -->
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Label Content="{Binding ElementName=myTreeView, Path=SelectedItem.Sample}"></Label>
</StackPanel>
</Grid>
</Window>

Categories