I'm implementing a custom designed FolderSelector and I'm using a TreeView to show the folders.
This is the view:
<TreeView Grid.Row="1" Background="Transparent" ItemsSource="{Binding Path=Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="viewModel:FolderViewModel" ItemsSource="{Binding Path=Items}">
<DockPanel>
<fa:FontAwesome Icon="FolderOutline" Foreground="#dd5d18" VerticalAlignment="Center" />
<Label Content="{Binding}" VerticalAlignment="Center" FontWeight="SemiBold" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
This is the viewmodel for the whole dialog
internal class FolderBrowserViewModel : BaseBAObject
{
#region Fields
#endregion
#region Properties
public ObservableCollection<FolderViewModel> Items { get; }
#endregion
#region Construction
public FolderBrowserViewModel()
{
Items = new ObservableCollection<FolderViewModel>();
LoadItems();
}
#endregion
#region Methods
private void LoadItems()
{
try
{
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
Items.Add(new FolderViewModel(drive.Name.Replace(#":\", ""), drive.Name));
}
}
catch (Exception)
{
// Ignore
}
}
#endregion
}
And this is the viewmodel for every folder
internal class FolderViewModel : BaseBAObject
{
#region Fields
private bool _isSelected;
private bool _isLoaded;
#endregion
#region Properties
public string Path { get; }
public string Name { get; }
public ObservableCollection<FolderViewModel> Items { get; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
LoadSubfolders();
}
}
}
#endregion
#region Construction
public FolderViewModel(string name, string path)
{
Path = path;
Name = name;
Items = new ObservableCollection<FolderViewModel>();
}
#endregion
#region Methods
public void LoadSubfolders(bool force = false)
{
if(_isLoaded && !force)
return;
try
{
var dirs = Directory.GetDirectories(Path);
Items.Clear();
foreach (var dir in dirs)
{
Items.Add(new FolderViewModel(dir, System.IO.Path.Combine(Path, dir)));
}
_isLoaded = true;
}
catch (Exception)
{
// ignore
}
}
public override string ToString()
{
return Name;
}
#endregion
}
I'm now facing several problems
The data template is not applied to the TreeView root objects nor childs
The childs are not displayed (they are properly loaded when a root item is selected)
I want to show a unlimited count of childs
Be explicit about the type argument, i.e. use {x:Type}, and bind the Content property of the Label to the Name property of the FolderViewModel:
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}" ItemsSource="{Binding Path=Items}">
<DockPanel>
<Label Content="{Binding Name}" VerticalAlignment="Center" FontWeight="SemiBold" />
</DockPanel>
</HierarchicalDataTemplate>
Related
Im trying to create a function in a wpf program, where I can select an item in a listview, and press a button and it changes the tabitem and allows me to then edit the item from the listview that was selected. Im having issues with getting the tabitem to change for me.
For the navigation of my app, I have a ViewModelBase, which my AppointmentsViewModel inherits from. Inside the AppointmentsViewVM there is a tabcontrol with 4 items, by clicking each one it loads the requested view/viewmodel for that function.
This is not the only way I've tried to get this to work, Im currently on day 4. I could get the TabIndex to change in the TabControl earlier, but the tab would still not change for me. So I abandoned that and tried the below route (still no luck).
ViewModelBase
namespace MBR2.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public ICommand MainMenuViewDogs_Command { get; set; }
public ICommand MainMenuViewAppointments_Command { get; set; }
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public ViewModelBase()
{
MainMenuViewDogs_Command = new BaseCommand(OpenDogs);
MainMenuViewAppointments_Command = new BaseCommand(OpenAppointments);
}
private void OpenDogs(object obj)
{
SelectedViewModel = new DogsViewModel();
}
private void OpenAppointments(object obj)
{
SelectedViewModel = new AppointmentsViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _SelectedIndexView;
public bool SelectedIndexView
{
get { return _SelectedIndexView; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexView");
}
}
private bool _SelectedIndexAdd;
public bool SelectedIndexAdd
{
get { return _SelectedIndexAdd; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexAdd");
}
}
private bool _SelectedIndexEdit;
public bool SelectedIndexEdit
{
get { return _SelectedIndexEdit; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexEdit");
}
}
private bool _SelectedIndexDelete;
public bool SelectedIndexDelete
{
get { return _SelectedIndexDelete; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexDelete");
}
}
}
}
AppointmentsViewModel
{
public class AppointmentsViewModel : ViewModelBase
{
private AppointmentsAddVM _AppointmentsAddVM;
public AppointmentsAddVM AppointmentsAddVM { get { return _AppointmentsAddVM; } }
private AppointmentsEditVM _AppointmentsEditVM;
public AppointmentsEditVM AppointmentsEditVM { get { return _AppointmentsEditVM; } }
private AppointmentsDeleteVM _AppointmentsDeleteVM;
public AppointmentsDeleteVM AppointmentsDeleteVM { get { return _AppointmentsDeleteVM; } }
private AppointmentsViewVM _AppointmentsViewVM;
public AppointmentsViewVM AppointmentsViewVM { get { return _AppointmentsViewVM; } }
public ObservableCollection<object> ViewModelList { get; set; }
public AppointmentsViewModel()
{
this.ViewModelList = new ObservableCollection<object>();
_AppointmentsAddVM = new AppointmentsAddVM();
_AppointmentsEditVM = new AppointmentsEditVM();
_AppointmentsDeleteVM = new AppointmentsDeleteVM();
_AppointmentsViewVM = new AppointmentsViewVM();
this.ViewModelList.Add(_AppointmentsAddVM);
this.ViewModelList.Add(_AppointmentsEditVM);
this.ViewModelList.Add(_AppointmentsDeleteVM);
this.ViewModelList.Add(_AppointmentsViewVM);
}
}
}
AppointmentsView.xaml
<UserControl
x:Class="MBR2.Views.AppointmentsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vms="clr-namespace:MBR2.ViewModels.Appointments"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:viewmodels="clr-namespace:MBR2.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:AppointmentsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:AppointmentsViewVM}">
<views:AppointmentsViewView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsAddVM}">
<views:AppointmentsAddView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsDeleteVM}">
<views:AppointmentsDeleteView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="Appointments" Width="Auto" Height="Auto">
<DockPanel HorizontalAlignment="Center"
Height="Auto"
LastChildFill="False"
VerticalAlignment="Top"
Width="Auto">
<TabControl x:Name="VMTabControl">
<TabItem x:Name="ViewTab"
TabIndex="0"
Header="View"
IsSelected="{Binding SelectedIndexView}"
Content="{Binding AppointmentsViewVM}"></TabItem>
<TabItem x:Name="AddTab"
TabIndex="1"
Header="Add"
IsSelected="{Binding SelectedIndexAdd}"
Content="{Binding AppointmentsAddVM}"></TabItem>
<TabItem x:Name="EditTab"
TabIndex="2"
Header="Edit"
IsSelected="{Binding SelectedIndexEdit}"
Content="{Binding AppointmentsEditVM}"></TabItem>
<TabItem x:Name="DeleteTab"
TabIndex="3"
Header="Delete"
IsSelected="{Binding SelectedIndexDelete}"
Content="{Binding AppointmentsDeleteVM}"></TabItem>
</TabControl>
</DockPanel>
</Grid>
</UserControl>
And the associated AppointmentsViewVM
namespace MBR2.ViewModels.Appointments
{
public class AppointmentsViewVM : ViewModelBase, INotifyPropertyChanged
{
private List<AppointmentsView_Wrapper> _AppointmentsView;
public List<AppointmentsView_Wrapper> AppointmentsView
{
get { return _AppointmentsView; }
set
{
_AppointmentsView = value;
OnPropertyChanged("AppointmentsView");
}
}
private List<string> _NameColumn = new List<string>();
public List<string> NameColumn
{
get { return _NameColumn; }
set
{
_NameColumn = value;
OnPropertyChanged("NameColumn");
}
}
private List<string> _ApptDateColumn = new List<string>();
public List<string> ApptDateColumn
{
get { return _ApptDateColumn; }
set
{
_ApptDateColumn = value;
OnPropertyChanged("ApptDateColumn");
}
}
private List<string> _ApptTimeColumn = new List<string>();
public List<string> ApptTimeColumn
{
get { return _ApptTimeColumn; }
set
{
_ApptTimeColumn = value;
OnPropertyChanged("ApptTimeColumn");
}
}
private List<string> _ApptVetColumn = new List<string>();
public List<string> ApptVetColumn
{
get { return _ApptVetColumn; }
set
{
_ApptVetColumn = value;
OnPropertyChanged("ApptVetColumn");
}
}
private List<string> _ApptCreatedColumn = new List<string>();
public List<string> ApptCreatedColumn
{
get { return _ApptCreatedColumn; }
set
{
_ApptCreatedColumn = value;
OnPropertyChanged("ApptCreatedColumn");
}
}
private List<int> _ApptIDColumn = new List<int>();
public List<int> ApptIDColumn
{
get { return _ApptIDColumn; }
set
{
_ApptIDColumn = value;
OnPropertyChanged("ApptIDColumn");
}
}
private string _AppointmentEdit_Enabled = "False";
public string AppointmentEdit_Enabled
{
get { return _AppointmentEdit_Enabled; }
set
{
_AppointmentEdit_Enabled = value;
OnPropertyChanged("AppointmentEdit_Enabled");
}
}
private AppointmentsView_Wrapper _ApptIDSelected;
public AppointmentsView_Wrapper ApptIDSelected
{
get { return _ApptIDSelected; }
set
{
AppointmentEdit_Enabled = "True";
_ApptIDSelected = value;
OnPropertyChanged("ApptIDSelected");
}
}
public AppointmentData AppointmentData = new AppointmentData();
public Messaging Messaging = new Messaging();
public ICommand AppointmentsListView_Command => new DelegateCommand<object>(AppointmentsListView_Clicked);
public ICommand EditSelection_Command => new DelegateCommand<object>(EditSelection_Clicked);
public AppointmentsViewVM()
{
BuildPage();
}
public async void BuildPage()
{
AppointmentsView = await AppointmentData.Appointments_GetAll();
foreach(var item in AppointmentsView)
{
ApptIDColumn.Add(item.ApptID);
NameColumn.Add(item.DogName);
ApptDateColumn.Add(item.ApptDate);
ApptTimeColumn.Add(item.ApptTime);
ApptVetColumn.Add(item.ApptVet);
ApptCreatedColumn.Add(item.ApptCreated.ToString("dd/mm/yyyy"));
}
}
public void AppointmentsListView_Clicked(object obj)
{
Messaging.ShowAlert(ApptIDSelected.ApptID.ToString());
}
public void EditSelection_Clicked(object obj)
{
bool result = Messaging.AskQuestion(ApptIDSelected.ApptID.ToString());
if(result)
{
SelectedIndexView = false;
SelectedIndexAdd = false;
SelectedIndexEdit = true;
SelectedIndexDelete = false;
OnPropertyChanged("SelectedIndexView");
OnPropertyChanged("SelectedIndexAdd");
OnPropertyChanged("SelectedIndexEdit");
OnPropertyChanged("SelectedIndexDelete");
}
else
{
Messaging.ShowAlert("no");
}
}
}
}
Here's a minimal reproduction of something where you select in a listbox and that then selects a corresponding tab in a tabcontrol.
This is very minimal but we can perhaps imagine a more sophisticated viewmodel per item in the listbox with name and viewmodel or something.
This is mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:Avm}">
<local:Aview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bvm}">
<local:Bview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cvm}">
<local:Cview/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ViewModels}"
x:Name="lb"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewModelName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ElementName=lb, Path=SelectedItem, Mode=TwoWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ViewModelName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I have only done 3 views and viewmodels.
Note that the selecteditem of the listbox is bound twoway to the tabcontrol.
I have matching views and viewmodels A, B and C
MainWindowViewModel
public class MainWindowViewmodel : ViewModelBase
{
public ObservableCollection<Object> ViewModels { get; set; } = new ObservableCollection<Object>{
new Avm{ViewModelName="A viewmodel" },
new Bvm{ViewModelName="B viewmodel" },
new Cvm{ViewModelName="C viewmodel" }
};
}
Both the itemssource of listbox and tabcontrol are bound to that collection of viewmodels. Which are, as I mentioned, as simple as you get really.
Viewmodelbase
public class ViewModelBase : INotifyPropertyChanged
{
public string ViewModelName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Avm, Bvm and Cvm just inherit from that.
An example usercontrol view.
<Grid>
<TextBlock Text="{Binding ViewModelName}"/>
</Grid>
</UserControl>
When I spin that up, select and select an item in the listbox the matching tab is selected. And vice versa. Select a tab and it selects the same one in the listbox.
I want to create a 2 level treview, with the root element of a string "Items":
-Items
-item1
-item2
-...
I have 2 classes to achieve this: ItemList, Item. I call them with the property CurrentItems.
private ItemList _currentItems = null;
public ItemList CurrentItems
{
get
{
return _currentItems;
}
set
{
if(_currentItems!=value)
{
SetProperty(ref _currentItems,
value, () => CurrentItems);
}
}
}
CurrentItems is initialized by creating a temporary ItemsList which is then filled with Items:
ItemList itemTempList = new ItemList();
...
while(...)
{
Item item = new Item();
...
itemTempList.Items.Add(item);
}
CurrentItems = itemTempList;
These are the two classes used:
public class Item
{
public long ObjectID { get; set; }
public string ItemName { get; set; }
}
public class ItemList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Item> items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get
{
return items;
}
set
{
items = value;
OnPropertyChanged();
}
}
private ObservableCollection<string> types = new ObservableCollection<string>();
public ObservableCollection<string> Types
{
get
{
if(types.Count > 0)
{
types.Add("Items");
}
return types;
}
set
{
types = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The View is initialized with a constructor:
public ItemView()
{
InitializeComponent();
}
And finally, this is the xaml:
<TreeView Name="itemTree" Grid.Row="1" Grid.Column="0" SelectedItemChanged="itemTree_SelectedItemChanged"
ItemsSource="{Binding CurrentItems}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<Label Content="{Binding Types,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Duplicate" Click="DuplicateCell"/>
<MenuItem Header="Delete" Click="DeleteCell"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
With my current understanding and code I am not able to create the TreeView.
Any help would be greatly appreciated!
I am displaying data with a structure similar to the folder structure on a disc and I want to filter it. If an item is unselected all items under that one are unselected too. If an item low in the tree is selected, all parents to up to the root are selected. The selection shall result in reloading and displaying the data matching the selected items.
For this I have created a TreeFilter class which is displayed a standard WPF TreeView.
When the selection is changed I want to build a list with selected items in the tree (I will extend the TreeFilter class with an additional property for that)
As the combination of items is fixed I created a static class which returns predefined sets of filters (trees).
I even managed to put the whole thing into a UserControl (I am still relatively new to c# and WPF) to be able to reuse it as I will need it in different parts of the solution. The control has a dependency property to bind the filter tree.
I don’t want to use code behind, as everything is MVVM
My problem is the following: How do I even notice, when an item is selected so I can reflect that change in updating the data? When any of those checkboxes is changed (on/off) I want to update the data-view
Thank you very much for your patience and support!
This is my code:
The TreeFilter class
public class DTTreeFilter : INotifyPropertyChanged
{
public string Caption { get; set; }
public string Description { get; set; }
bool isEnabled;
public bool IsEnabled
{
get => isEnabled;
set
{
if (value != this.isEnabled)
{
this.isEnabled = value;
if (this.isEnabled == false)
{
foreach (var child in children)
{
child.IsEnabled = false;
}
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEnabled)));
}
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return isExpanded; }
set
{
if (value != isExpanded)
{
isExpanded = value;
NotifyPropertyChanged(nameof(IsExpanded));
}
}
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (value != isSelected)
{
isSelected = value;
NotifyPropertyChanged(nameof(IsSelected));
}
}
}
private bool isFilterSelected;
public bool IsFilterSelected
{
get { return isFilterSelected; }
set
{
if (value != isFilterSelected)
{
isFilterSelected = value;
if (this.isFilterSelected == false)
{
foreach (var child in children)
{
child.IsFilterSelected = false;
}
}
// Filter ein -> alle Eltern einschalten
if (this.isFilterSelected == true)
{
if (this.Parent != null)
this.Parent.IsFilterSelected = true;
}
NotifyPropertyChanged(nameof(IsFilterSelected));
}
}
}
public DTTreeFilter Parent { get; set; }
ObservableCollection<DTTreeFilter> children;
public ObservableCollection<DTTreeFilter> Children
{
get => children;
set => children = value;
}
public static DTTreeFilter Create(string Caption, string Description, DTTreeFilter Parent = null)
{
return new DTTreeFilter(Caption, Description, Parent);
}
public DTTreeFilter(string Caption, string Description, DTTreeFilter Parent = null)
{
children = new ObservableCollection<DTTreeFilter>();
this.Caption = Caption;
this.Description = Description;
this.IsEnabled = true;
if (Parent != null)
this.Parent = Parent;
}
public void AddChild(DTTreeFilter child)
{
child.Parent = this;
children.Add(child);
this.IsExpanded = true;
}
public override string ToString()
{
return $"ToString {this.Caption }";
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
The TreeView
<TreeView
x:Name="trFilter"
ItemsSource="{Binding FilterList}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:DTTreeFilter}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsFilterSelected}"
IsEnabled="{Binding IsEnabled}"
VerticalContentAlignment="Center"/>
<TextBlock Text="{Binding Caption}"
VerticalAlignment="Center"
FontWeight="DemiBold"
FontSize="14"
/>
<TextBlock Text=" " />
<TextBlock Text="{Binding Description}"
VerticalAlignment="Center"
Width="250"
FontSize="11"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The prepared Filterset
public static DTTreeFilter GetFilterSetA()
{
DTTreeFilter rootItem = DTTreeFilter.Create("Root Item", "this is the root item");
DTTreeFilter childItemA = DTTreeFilter.Create("Child A", "this is child A");
DTTreeFilter childItemB = DTTreeFilter.Create("Child B", "this is child B");
rootItem.AddChild(childItemA);
rootItem.AddChild(childItemB);
DTTreeFilter childItemA1 = DTTreeFilter.Create("Child A1", "this is child A1");
DTTreeFilter childItemA2 = DTTreeFilter.Create("Child A1", "this is child A2");
childItemA.AddChild(childItemA1);
childItemA.AddChild(childItemA2);
return rootItem;
}
The ViewModel
public class TreeFilterTestViewModel
{
public virtual ObservableCollection<DTTreeFilter> AllFilters { get; set; } = new ObservableCollection<DTTreeFilter>();
public TreeFilterTestViewModel()
{
AllFilters.Add( DTSystemFilter.GetFilterSetA() );
}
resulting in
enter image description here
I have a Button which shows a ContextMenu look like
Here the XAML:
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Data.OnSelected, Source={StaticResource proxy}}" />
</Style>
<Button Name="ButtonMenu_Export"
Click="ButtonMenu_Export_Click"
Visibility="{Binding ButtonExportEnabled,
Converter={StaticResource VisibilityConverter}}">
<StackPanel Orientation="Vertical">
<Image Source="...." />
<TextBlock Width="70" TextAlignment="Center" Text="Export" />
</StackPanel>
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding ExportMenuItems}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Text}" RecognizesAccessKey="True" />
<HierarchicalDataTemplate.ItemsSource>
<Binding Path="SubItems" />
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
The menu is created at runtime using this List (as in this article)
public System.Collections.Generic.List<MenuItem> ExportMenuItems
{
get { return _menuService.GetParentMenuItems(); }
}
Now, what I cannot do is bind the items to the OnSelected command of MenuItem class.
The class which defines the menu is:
public class MenuItem
{
private string name;
private string text;
private int menuId;
private ICommand onSelected;
private MenuItem parent;
private ObservableCollection<MenuItem> subItems;
public MenuItem(string name, string text, int MenuId)
{
this.menuId = MenuId;
this.name = name;
this.text = text;
this.subItems = new ObservableCollection<MenuItem>();
}
public string Name { get { return this.name; } }
public string Text { get { return this.text; } }
public MenuItem Parent { get { return this.parent; } set { this.parent = value; } }
public ICommand OnSelected
{
get
{
if (this.onSelected == null)
this.onSelected = new MenuCommand(this.ItemSelected, this.ItemCanBeSelected, menuId);
return this.onSelected;
}
}
public ObservableCollection<MenuItem> SubItems
{
get { return this.subItems; }
}
}
I created a proxy class as in this article to made DataContext visible to HierarchicalDataTemplate content but maybe I misunderstood something:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Where I'm wrong?
Change your command binding
Command="{Binding Data.OnSelectedd,Source={StaticResource proxy}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}"
I am trying to create a File Explorer style TreeView/ListView window that will allow the user to select a "Project" from within a "ProjectFolder".
The "ProjectFolder" tree is bound to a property on my ViewModel called "RootProjectFolders" and the "Project" list is bound to a property called "ProjectsInSelectedFolder". Things were mostly working; however, I was getting null exceptions when I first loaded the window because the "SelectedFolder" had not yet been set. When I tried to implement a simple check to make sure that the "SelectedFolder" was not null, my "Project" ListView stopped refreshing.
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
If I remove (this.SelectedFolder != null) from the above, the ListView will update, but I will get an NullException error. Why is that check breaking my binding?
Following up on the request for additional information, here is the XAML of the TreeView and ListView that are binding to the properties on the ViewModel:
<TreeView Name="treeviewProjectFolders" Grid.Column="0"
ItemsSource="{Binding Path=RootProjectFolders}">
<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.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<GridSplitter Name="splitterProjects" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<ListView Name="listviewProjects" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Path=ProjectsInSelectedFolder}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ViewModel
public class SelectProjectViewModel : ViewModelBase
{
#region Fields
List<ProjectViewModel> _projectsInSelectedFolder;
List<ProjectFolderViewModel> _rootProjectFolders;
static ProjectFolderViewModel _selectedFolder = null;
ProjectViewModel _selectedProject;
#endregion // Fields
#region Constructor
public SelectProjectViewModel(ProjectFolders rootProjectFolders)
{
if (_rootProjectFolders != null) { _rootProjectFolders.Clear(); }
_rootProjectFolders = new List<ProjectFolderViewModel>();
foreach (ProjectFolder rootFolder in rootProjectFolders)
{
_rootProjectFolders.Add(new ProjectFolderViewModel(rootFolder, this));
}
_projectsInSelectedFolder = new List<ProjectViewModel>();
// Subscribe to events
this.PropertyChanged += OnPropertyChanged;
}
#endregion // Constructor
#region Properties
public List<ProjectFolderViewModel> RootProjectFolders
{
get
{
return _rootProjectFolders;
}
}
public List<ProjectViewModel> ProjectsInSelectedFolder
{
get
{
return _projectsInSelectedFolder;
}
}
public ProjectFolderViewModel SelectedFolder
{
get
{
return _selectedFolder;
}
set
{
if (_selectedFolder != value)
{
_selectedFolder = value;
}
}
}
public ProjectViewModel SelectedProject
{
get
{
return _selectedProject;
}
set
{
_selectedProject = value;
base.RaisePropertyChangedEvent("SelectedProject");
}
}
#endregion // Properties
#region Methods
public void FindSelectedFolder(ProjectFolderViewModel root)
{
if (root.IsSelected) { _selectedFolder = root; }
else
{
foreach (ProjectFolderViewModel folder in root.Children)
{
if (_selectedFolder == null)
{
FindSelectedFolder(folder);
}
}
}
}
#endregion // Methods
#region Event Handlers
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SelectedFolder":
_selectedFolder = null;
foreach (ProjectFolderViewModel root in this.RootProjectFolders)
{
if (_selectedFolder == null)
{
this.FindSelectedFolder(root);
}
}
_projectsInSelectedFolder.Clear();
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
break;
}
}
#endregion // Event Handlers
Also, here is the ViewModel for the individual project folders that are used to raise the "SelectedFolder" property:
public class ProjectFolderViewModel : ViewModelBase
{
#region Fields
ReadOnlyCollection<ProjectFolderViewModel> _children;
List<ProjectFolderViewModel> _childrenList;
bool _isExpanded;
bool _isSelected;
ProjectFolderViewModel _parentNode;
SelectProjectViewModel _parentTree;
ProjectFolder _projectFolder;
#endregion // Fields
#region Constructor
public ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree) : this(projectFolder, parentTree, null)
{ }
private ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree, ProjectFolderViewModel parentNode)
{
_projectFolder = projectFolder;
_parentTree = parentTree;
_parentNode = parentNode;
_childrenList = new List<ProjectFolderViewModel>();
foreach (ProjectFolder child in _projectFolder.ChildFolders)
{
_childrenList.Add(new ProjectFolderViewModel(child, _parentTree));
}
_children = new ReadOnlyCollection<ProjectFolderViewModel>(_childrenList);
}
#endregion // Constructor
#region Properties
public ReadOnlyCollection<ProjectFolderViewModel> Children
{
get
{
return _children;
}
}
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parentNode != null)
_parentNode.IsExpanded = true;
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
base.RaisePropertyChangedEvent("IsSelected");
//if (_isSelected)
//{
_parentTree.RaisePropertyChangedEvent("SelectedFolder");
//}
}
}
public string Name
{
get
{
return _projectFolder.Name;
}
}
public ProjectFolder ProjectFolder
{
get
{
return _projectFolder;
}
}
#endregion // Properties
Change all your
List<T> to observablecollection<T>
because when ever there is new file or folder your adding the Item, your not creating new List, since observablecollection implements INotifyCollectionChanged, and INotifyPropertyChanged it'll internally take care of notifying and refreshing the View. But list cant do that