0

I have a tree view which lists the drives & folders in the PC. I am working in MVVM with Micrososft MVVM Toolkit, Fody and Microsoft Behaviors.

I have a Folder Tree view model for PC which creates an Observable Collection of Folder Items represented by a FolderItemViewModel. The simplified test example shown here is started from App.xaml.cs.

I want a context menu similar to File Explorer. I can put a context menu at the tree view items level but I need to change properties in the FolderViewModel which I cannot do from the FolderItemViewModel.

I was hoping Iteractive Triggers would help as it does for Selected Item.

Any assistance will be apreciated.

Xaml:

"""

    <TreeView x:Name="tvFolders" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding FolderCollection}"
              MinWidth="250" MinHeight="250">

        <ie1:Interaction.Triggers >
            <ie1:EventTrigger EventName ="SelectedItemChanged" >
                <ie1:InvokeCommandAction CommandParameter="{Binding ElementName=tvFolders, Path=SelectedItem}" 
                                        Command="{Binding TreeSelectionChangedClick}" />
            </ie1:EventTrigger>
        </ie1:Interaction.Triggers >

        <TreeView.Resources>
            <ContextMenu x:Key="TreeViewMenu">
                <MenuItem Command="{Binding NewFolderClick}" Header=" New Folder " />
                <MenuItem Command="{Binding RenameClick}" Header=" Rename " />
            </ContextMenu>
        </TreeView.Resources>

        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}" >
                <Setter Property="ContextMenu" Value="{StaticResource TreeViewMenu}" />
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True" >
                        <Setter Property="FontWeight" Value="Bold" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TreeView.ItemContainerStyle>

        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate  >
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>

    <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" >
        <TextBlock Text="Selected Folder:  " />
        <TextBox Text="{Binding SelectedFolderPath}" />
    </StackPanel>
    

"""

FolderViewModel:

""" internal class FolderTreeViewModel : ObservableObject { public ObservableCollection FolderCollection { get; set; }

    public string SelectedFolderPath { get; set; }

    public ICommand TreeSelectionChangedClick { get; set; }

    public FolderTreeViewModel()
    {
        List<FolderItemViewModel> FolderItems  = new List<FolderItemViewModel>();

        // Get the logical drives as a list of DirectoryItemModel
        List<FolderItemModel> subFolders = GetLogicalDrives();
        foreach (FolderItemModel item in subFolders)
        {
        FolderItemViewModel folderItemViewModel = new FolderItemViewModel(item.FullPath);
        FolderItems.Add(folderItemViewModel);
        }
        FolderCollection = new ObservableCollection<FolderItemViewModel>(FolderItems);

        TreeSelectionChangedClick = new RelayCommand<FolderItemViewModel>(TreeSelectionChangedCommand);
    }

    public static List<FolderItemModel> GetLogicalDrives()
    {
        // Get every logical drive on the machine
        List<FolderItemModel> DirectoryItems = new List<FolderItemModel>();
        string[] drives = Directory.GetLogicalDrives();
        foreach (string drive in drives)
        {
        DirectoryItems.Add(new FolderItemModel { FullPath = drive});

        }
        return DirectoryItems;  // Directory.GetLogicalDrives().Select(drive => new DirectoryItemModel { FullPath = drive, Type = DirectoryItemType.Drive }).ToList();
    }

    private void TreeSelectionChangedCommand(FolderItemViewModel tVI)
    {
        if (TreeSelectionChangedClick != null)
        {
        SelectedFolderPath = tVI.FullPath;
        }
    }

} """

FolerItemViewModel:

""" namespace TestTreeViewContextMenu.ViewModels { internal class FolderItemViewModel { public string Name { get; set; } public string FullPath { get; set; } public ObservableCollection SubFolders { get; set; }

    public ICommand NewFolderClick { get; set; }

    public ICommand RenameClick { get; set; }

    public FolderItemViewModel(string fullName)
    {
        FullPath = fullName; 
        if (fullName == Path.GetPathRoot(fullName))
        {
        Name = fullName;
        }
        else
        Name = Path.GetFileName(fullName);
        SubFolders = new ObservableCollection<FolderItemViewModel>();   

        NewFolderClick = new RelayCommand(NewFolderCommand);
        RenameClick = new RelayCommand(RenameCommand);
    }

    private void NewFolderCommand()
    {
        MessageBox.Show("New Folder");
        // Cannot update the FolderTreeViewModel ColderCollection from FolderItemViewModel
    }

    private void RenameCommand()
    {
        MessageBox.Show($"Rename");
        // Cannot update the SelectedFolderPath value from here
    }
}

} """

Thepointyman
  • 121
  • 1
  • 7

1 Answers1

0

I have cracked it by using an interaction event 'PreviewMouseRightButtonDown' in the TextBlock of the Treeview.ItemTemplate. I have added a new property 'RightSelected' to the FolderItemViewModel which is set to 'false' in the constructor. When the right mouse button is depressed this is set to 'true'. This event happens before the Context Menu is displayed. The command triggered from the context menu in FolderTreeViewModel searches the FolderCollection for the FolderItem with 'RightSelected == true' and set this FolderItem to be subject of the command. If the mouse is clicked outside the TextBlock showing the item the no FolderItems will have 'RightSelected' property set. The search routine must check for this condition. The selected FolderItem is saved in '_lastRightSelected' and cleared at the start of each search.

View:

<Window x:Class="TestTreeViewContextMenu.Views.FolderTreeView"
    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:ie="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:ie1="http://schemas.microsoft.com/xaml/behaviors" 
    xmlns:local="clr-namespace:TestTreeViewContextMenu"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="10" />
        <RowDefinition Height="auto" />
        <RowDefinition Height="auto" />
        <RowDefinition Height="10" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="10" />
        <ColumnDefinition Width="auto" />
        <ColumnDefinition Width="10" />
    </Grid.ColumnDefinitions>

    <TreeView x:Name="tvFolders" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding FolderCollection}"
              MinWidth="250" MinHeight="250">

        <TreeView.ContextMenu >
            <ContextMenu>
                <MenuItem Header=" New Folder " Command="{Binding NewFolderClick}" />
                <MenuItem Header="RENAME" Command="{Binding RenameClick}" />
            </ContextMenu>
        </TreeView.ContextMenu>
        
        <ie1:Interaction.Triggers >
            <ie1:EventTrigger EventName ="SelectedItemChanged" >
                <ie1:InvokeCommandAction 
                    CommandParameter="{Binding ElementName=tvFolders, Path=SelectedItem}" 
                    Command="{Binding TreeSelectionChangedClick}" />
            </ie1:EventTrigger>
            
        </ie1:Interaction.Triggers >

        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate  >
                <TextBlock Text="{Binding Name}" >

                    <ie1:Interaction.Triggers>
                        <ie1:EventTrigger EventName="PreviewMouseRightButtonDown">
                            <ie1:ChangePropertyAction PropertyName="RightSelected" Value="true" TargetObject="{Binding}" />
                        </ie1:EventTrigger>
                    </ie1:Interaction.Triggers>
                    
                </TextBlock>

            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>

    <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" >
        <TextBlock Text="Selected Folder:  " />
        <TextBox Text="{Binding SelectedFolderPath}" />
    </StackPanel>
    
</Grid>

FolderTreeViewModel:

    using Microsoft.Toolkit.Mvvm.ComponentModel;
    using Microsoft.Toolkit.Mvvm.Input;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using TestTreeViewContextMenu.Model;

    namespace TestTreeViewContextMenu.ViewModels
    {
        internal class FolderTreeViewModel : ObservableObject
        {
            public ObservableCollection<FolderItemViewModel> FolderCollection { get; set; }

            public string SelectedFolderPath { get; set; }

            public ICommand TreeSelectionChangedClick { get; set; }

            public ICommand NewFolderClick { get; set; }

            public ICommand RenameClick { get; set; }

            public ICommand SelectedFolderClick { get; set; }

            public FolderItemViewModel SelectedFolderItem { get; set; }

            public ICommand RightMouseButtonDownClick { get; set; }

            public bool IsSelected { get; set; }
        
            FolderItemViewModel _lastRightSelected = null;

            public FolderTreeViewModel()
            {
                List<FolderItemViewModel> FolderItems  = new List<FolderItemViewModel>();

                // Get the logical drives as a list of DirectoryItemModel
                List<FolderItemModel> subFolders = GetLogicalDrives();
                foreach (FolderItemModel item in subFolders)
                {
                FolderItemViewModel folderItemViewModel = new FolderItemViewModel(item.FullPath);
                FolderItems.Add(folderItemViewModel);
                }
                FolderCollection = new ObservableCollection<FolderItemViewModel>(FolderItems);

                TreeSelectionChangedClick = new RelayCommand<FolderItemViewModel>(TreeSelectionChangedCommand);
                NewFolderClick = new RelayCommand(NewFolderCommand);
                RenameClick = new RelayCommand(RenameCommand);
                RightMouseButtonDownClick = new RelayCommand<FolderItemViewModel>(RightMouseButtonDownCommand);
            }

            public List<FolderItemModel> GetLogicalDrives()
            {
                // Get every logical drive on the machine
                List<FolderItemModel> DirectoryItems = new List<FolderItemModel>();
                string[] drives = Directory.GetLogicalDrives();
                foreach (string drive in drives)
                {
                DirectoryItems.Add(new FolderItemModel { FullPath = drive});

                }
                return DirectoryItems; 
            }

            private void RightMouseButtonDownCommand(FolderItemViewModel tVI)
            {
                SelectedFolderItem = tVI;
            }

            private void TreeSelectionChangedCommand(FolderItemViewModel tVI)
            {
                if (TreeSelectionChangedClick != null)
                {
                SelectedFolderPath = tVI.FullPath;
                    SelectedFolderItem = tVI;
                }
            }

            private void NewFolderCommand()
            {
                FindRightSelected();

                string folderItemFullName = "";
                if (_lastRightSelected == null)
                    folderItemFullName = "";
                else
                    folderItemFullName = _lastRightSelected.FullPath;

                MessageBox.Show($"New Folder in FolderTreeVM {folderItemFullName}");
                // Cannot update the FolderTreeViewModel ColderCollection from FolderItemViewModel
            }

            private void RenameCommand()
            {
                FindRightSelected();

                string folderItemFullName = "";
                if (_lastRightSelected == null)
                    folderItemFullName = "";
                else
                    folderItemFullName = _lastRightSelected.FullPath;


                MessageBox.Show($"Rename in FolderTreeVM {folderItemFullName} ");
                // Cannot update the SelectedFolderPath value from here
            }

            private void FindRightSelected()
            {
                FolderItemViewModel folderItemViewModel = null;
                if (_lastRightSelected != null)
                {
                    _lastRightSelected.RightSelected = false;
                }

                foreach (FolderItemViewModel tVI in FolderCollection)
                {
                    if (tVI.RightSelected)
                    {
                        folderItemViewModel = tVI;
                        break;
                    }
                }
                _lastRightSelected = folderItemViewModel ?? null;
            }

            private void SelectedItemCommand(FolderItemViewModel tVi)
            {
                SelectedFolderItem = tVi;
            }

        }
    }

FolderItemViewModel:

using Microsoft.Toolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using TestTreeViewContextMenu.Model;

namespace TestTreeViewContextMenu.ViewModels
{
    internal class FolderItemViewModel
    {
        public string Name { get; set; }
        public string FullPath { get; set; }
        public bool RightSelected { get; set; }
        public ObservableCollection<FolderItemViewModel> SubFolders { get; set; }

        public FolderItemViewModel(string fullName)
        {
            FullPath = fullName; 
            if (fullName == Path.GetPathRoot(fullName))
                Name = fullName;
            else
                Name = Path.GetFileName(fullName);
            RightSelected = false;
            SubFolders = new ObservableCollection<FolderItemViewModel>();   

        }
    }
}

Thanks to Mark Liversage who pointed me in the right direction in this answer: Select TreeView Node on right click before displaying ContextMenu

Thepointyman
  • 121
  • 1
  • 7