TreeView Expanden zum vorher definierten Item

  • WPF

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    TreeView Expanden zum vorher definierten Item

    Hi,
    ich hab gerade ein Problemchen bei dem ich mir, selbst nach einiger Zeit googlen, die Zähne ausbeiße.
    Ich hab ein TreeView welches mir alle Unterordner und Dateien in einem vorgegeben Pfad auflistet, soweit so gut.
    Bei Klick auf eine Datei wird diese in einem TabPage geöffnet, passt auch.
    Nun hab ich in den Settings ein Propertie LastOpenFile im MainViewModel weise ich dem SelectedItem den Pfad aus LastOpenFile zu und öffne die Datei im entsprechenden TabPage (siehe ReloadDate() ab Zeile 194.
    Das funktioniert alles bestens, doch wie kann ich nun dem TreeView verklickern, das es bis zu diesem Selected Item expanden soll? (die IsSelected und IsExpanden Properties sind im DirectoryItemViewModel.

    Ich nutze folgende ViewModels:
    Das MainViewModel mit dem Selected Item:
    MainViewModel

    C#-Quellcode

    1. public class MainViewModel : ViewModelBase
    2. {
    3. #region Private Properties
    4. private string _path => IoC.Application.CodeFolderPath;
    5. #endregion
    6. #region Constructor
    7. public MainViewModel()
    8. {
    9. if (!string.IsNullOrEmpty(_path))
    10. {
    11. ReloadData();
    12. ClearTabPages();
    13. InitCommands();
    14. }
    15. }
    16. #endregion
    17. #region Public Properties
    18. public ObservableCollection<DirectoryItemViewModel> DirectoryItems { get; set; }
    19. public DirectoryItemViewModel SelectedItem { get; set; }
    20. public ObservableCollection<TabPageViewModel> TabPages { get; set; }
    21. public TabPageViewModel SelectedTab { get; set; }
    22. public string RootFolderName => _path.GetFileFolderName();
    23. public bool CanCloseAllTabPages => TabPages.Count > 0;
    24. public bool CanAddNewFileOrFolder => SelectedItem != null && SelectedItem.Type == DirectoryItemTypeEnum.Folder;
    25. public bool CanSave => FileFolderName.Length > 0;
    26. public bool HasItems => DirectoryItems.Count > 0;
    27. public bool OnCreate { get; set; }
    28. public string TextEntryLabel { get; set; }
    29. public string FileFolderName { get; set; } = string.Empty;
    30. #endregion
    31. #region Events
    32. private void DirectoryItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    33. {
    34. OnPropertyChanged(nameof(CanAddNewFileOrFolder));
    35. OnPropertyChanged(nameof(HasItems));
    36. }
    37. private void TabPages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    38. {
    39. OnPropertyChanged(nameof(CanCloseAllTabPages));
    40. }
    41. #endregion
    42. #region Commands
    43. public ICommand OpenFileCommand { get; set; }
    44. public ICommand CloseTabCommand { get; set; }
    45. public ICommand CloseAllTabsCommand { get; set; }
    46. public ICommand AddNewFileCommand { get; set; }
    47. public ICommand AddNewFolderCommand { get; set; }
    48. public ICommand ExpandAndCollapsAllCommand { get; set; }
    49. public ICommand OpenRootFolderCommand { get; set; }
    50. public ICommand SaveFileOrFolderCommand { get; set; }
    51. public ICommand CancelFileOrFolderCommand { get; set; }
    52. #endregion
    53. #region Comamnd Methods
    54. private void OpenFile(object param)
    55. {
    56. SelectedItem = param as DirectoryItemViewModel;
    57. if (SelectedItem.Type != DirectoryItemTypeEnum.File) return;
    58. TabPageViewModel existingTab = TabPages.Where(x => x.Tag == SelectedItem.FullPath).FirstOrDefault();
    59. if (existingTab != null)
    60. SelectedTab = existingTab;
    61. else
    62. {
    63. var newTab = new TabPageViewModel(SelectedItem.Name, SelectedItem.FullPath,
    64. new EditorViewModel (SelectedItem.FullPath));
    65. TabPages.Add(newTab);
    66. SelectedTab = newTab;
    67. }
    68. SelectedItem.IsSelected = true;
    69. }
    70. private void CloseTab(object param)
    71. {
    72. var tabToClose = param as TabPageViewModel;
    73. if(tabToClose.Editor.IsModified)
    74. {
    75. //TODO: Über IoC MessageBox aufrufen und dort das Speichern abfragen
    76. DirectoryStructure.SaveContent(tabToClose.Tag, tabToClose.Editor.Content);
    77. }
    78. TabPages.Remove(tabToClose);
    79. if (TabPages.Count <= 0)
    80. ClearTabPages();
    81. }
    82. private void AddNewFileOrFolder(string arg)
    83. {
    84. if (SelectedItem != null && SelectedItem.Type == DirectoryItemTypeEnum.Folder)
    85. {
    86. if (arg == "Neue Datei")
    87. {
    88. var newFile = new DirectoryItemViewModel(SelectedItem.FullPath + @"\" + FileFolderName, DirectoryItemTypeEnum.File);
    89. SelectedItem.ChildItems.Add(newFile);
    90. var newTab = new TabPageViewModel(newFile.Name, newFile.FullPath,
    91. new EditorViewModel("Code goes here", newFile.FullPath) { IsModified = true });
    92. DirectoryStructure.SaveContent(newTab.Tag, newTab.Editor.Content);
    93. TabPages.Add(newTab);
    94. SelectedTab = newTab;
    95. }
    96. else
    97. {
    98. DirectoryStructure.CreateDirectories(SelectedItem.FullPath + @"\" + FileFolderName);
    99. ReloadData();
    100. }
    101. }
    102. FileFolderName = string.Empty;
    103. OnCreate = false;
    104. }
    105. private void CloseAllTabPages()
    106. {
    107. if(TabPages.Any(t => t.Editor.IsModified))
    108. {
    109. //TODO: Messagebox zum speichern
    110. }
    111. ClearTabPages();
    112. }
    113. private void ExpandAndCollapsAll()
    114. {
    115. foreach (var item in DirectoryItems)
    116. {
    117. if (item.Type == DirectoryItemTypeEnum.Folder)
    118. item.IsExpanded ^= true;
    119. }
    120. }
    121. public void OpenRootFolder()
    122. {
    123. //TODO: FolderBrowserDialog aufrufen
    124. CloseAllTabPages();
    125. ReloadData();
    126. OnPropertyChanged(nameof(RootFolderName));
    127. }
    128. private void Edit(string label)
    129. {
    130. TextEntryLabel = label;
    131. OnCreate = true;
    132. }
    133. private void Cancel()
    134. {
    135. FileFolderName = string.Empty;
    136. OnCreate = false;
    137. }
    138. #endregion
    139. #region Helper Methods
    140. private void ClearTabPages()
    141. {
    142. TabPages = new ObservableCollection<TabPageViewModel>();
    143. TabPages.CollectionChanged += TabPages_CollectionChanged;
    144. }
    145. private void ReloadData()
    146. {
    147. DirectoryItems = new ObservableCollection<DirectoryItemViewModel>(_path.GetFilesFolders().Select(f => new DirectoryItemViewModel(f.FullPath, f.Type)));
    148. DirectoryItems.CollectionChanged += DirectoryItems_CollectionChanged;
    149. SelectedItem = new DirectoryItemViewModel(IoC.Settings.LastOpenFile, DirectoryItemTypeEnum.File);
    150. SelectedItem.IsSelected = true;
    151. SelectedTab = new TabPageViewModel(SelectedItem.Name, SelectedItem.FullPath, new EditorViewModel(SelectedItem.FullPath));
    152. TabPages.Add(SelectedTab);
    153. }
    154. private void InitCommands()
    155. {
    156. OpenFileCommand = new RelayCommand(o => OpenFile(o));
    157. CloseTabCommand = new RelayCommand(c => CloseTab(c));
    158. CloseAllTabsCommand = new RelayCommand(c => CloseAllTabPages()).ObservesCanExecute(() => CanCloseAllTabPages);
    159. AddNewFileCommand = new RelayCommand(a => Edit("Neue Datei")).ObservesCanExecute(() => CanAddNewFileOrFolder);
    160. AddNewFolderCommand = new RelayCommand(a => Edit("Neuer Ordner")).ObservesCanExecute(() => CanAddNewFileOrFolder);
    161. ExpandAndCollapsAllCommand = new RelayCommand(e => ExpandAndCollapsAll());
    162. OpenRootFolderCommand = new RelayCommand(o => OpenRootFolder());
    163. SaveFileOrFolderCommand = new RelayCommand(s => AddNewFileOrFolder(TextEntryLabel)).ObservesCanExecute(() => CanSave);
    164. CancelFileOrFolderCommand = new RelayCommand(c =>Cancel());
    165. }
    166. #endregion
    167. }



    Das DirectoryItemViewModel für ein einzelnes Item:
    DirectoryItemViewModel

    C#-Quellcode

    1. public class DirectoryItemViewModel : ViewModelBase
    2. {
    3. #region Constructor
    4. /// <summary>
    5. /// default constructor
    6. /// </summary>
    7. public DirectoryItemViewModel()
    8. {
    9. }
    10. public DirectoryItemViewModel(string fullpath, DirectoryItemTypeEnum type)
    11. {
    12. FullPath = fullpath;
    13. Type = type;
    14. ClearDirectoryItems();
    15. InitCommands();
    16. }
    17. #endregion
    18. #region Public Properties
    19. #region Model Properties
    20. public string FullPath { get; set; }
    21. public string Name => Type == DirectoryItemTypeEnum.Drive ? FullPath : FullPath.GetFileFolderName();
    22. public DirectoryItemTypeEnum Type { get; set; }
    23. #endregion
    24. #region UI Properties
    25. public string ImageName => Type == DirectoryItemTypeEnum.Drive ? "drive" : (Type == DirectoryItemTypeEnum.File ? "file" : (IsExpanded ? "folder-open" : "folder-close"));
    26. public ObservableCollection<DirectoryItemViewModel> ChildItems { get; set; }
    27. public bool CanOpen => Type == DirectoryItemTypeEnum.File;
    28. public bool CanExpand => Type != DirectoryItemTypeEnum.File;
    29. public bool IsSelected { get; set; }
    30. public bool IsExpanded
    31. {
    32. get => ChildItems?.Count(i => i != null) > 0;
    33. set
    34. {
    35. if (value == true)
    36. Expand();
    37. else
    38. ClearDirectoryItems();
    39. }
    40. }
    41. #endregion
    42. #endregion
    43. #region Commands
    44. public ICommand ExpandCommand { get; set; }
    45. #endregion
    46. #region Command Methods
    47. private void Expand()
    48. {
    49. if (Type == DirectoryItemTypeEnum.File)
    50. return;
    51. ChildItems = new ObservableCollection<DirectoryItemViewModel>(FullPath.GetFilesFolders().Select(item => new DirectoryItemViewModel(item.FullPath, item.Type)));
    52. }
    53. #endregion
    54. #region Helper Methods
    55. private void InitCommands()
    56. {
    57. ExpandCommand = new RelayCommand(e => Expand());
    58. }
    59. private void ClearDirectoryItems()
    60. {
    61. ChildItems = new ObservableCollection<DirectoryItemViewModel>();
    62. if (Type != DirectoryItemTypeEnum.File)
    63. ChildItems.Add(null);
    64. }
    65. #endregion
    66. }



    dann nmoch die entsprechende Stelle im TreeView:
    TreeView Xaml

    XML-Quellcode

    1. <!-- #region TreeView (Row 1) -->
    2. <TreeView x:Name="tv" ItemsSource="{Binding DirectoryItems}" Grid.Row="2" BorderThickness="0" Background="{StaticResource BackgroundVeryLightBrush}">
    3. <TreeView.Resources>
    4. <LinearGradientBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" EndPoint="0,1" StartPoint="0,0">
    5. <GradientStop Color="{StaticResource DefaultLightDark}" Offset="0"/>
    6. <GradientStop Color="{StaticResource DefaultLight}" Offset="1"/>
    7. </LinearGradientBrush>
    8. <LinearGradientBrush x:Key="{x:Static SystemColors.ControlBrushKey}" EndPoint="0,1" StartPoint="0,0">
    9. <GradientStop Color="#FFF8F8F8" Offset="0"/>
    10. <GradientStop Color="#FFE5E5E5" Offset="1"/>
    11. </LinearGradientBrush>
    12. <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
    13. <SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
    14. </TreeView.Resources>
    15. <TreeView.ItemContainerStyle>
    16. <Style TargetType="{x:Type TreeViewItem}">
    17. <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    18. <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    19. <Style.Triggers>
    20. <Trigger Property="IsSelected" Value="True">
    21. <Setter Property="Background" Value="{StaticResource BackgroundGradientBrush}"/>
    22. </Trigger>
    23. <Trigger Property="IsMouseOver" Value="True">
    24. <Setter Property="Background" Value="{StaticResource BackgroundGradientBrush}"/>
    25. </Trigger>
    26. </Style.Triggers>
    27. <Style.Resources>
    28. <Style TargetType="{x:Type Border}">
    29. <Setter Property="CornerRadius" Value="2"/>
    30. </Style>
    31. </Style.Resources>
    32. </Style>
    33. </TreeView.ItemContainerStyle>
    34. <TreeView.ItemTemplate>
    35. <HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">
    36. <StackPanel Orientation="Horizontal" Margin="5 0 10 0">
    37. <Image Width="20" Margin="3" Source="{Binding ImageName, Converter={local:HeaderTextToImageConverter}}"/>
    38. <TextBlock Text="{Binding Name, UpdateSourceTrigger=LostFocus}"
    39. VerticalAlignment="Center"
    40. Foreground="{StaticResource CharDarkBlueBrush}"
    41. FontSize="{StaticResource FontSizeRegular}"
    42. Background="Transparent"
    43. Padding="2" >
    44. <TextBlock.InputBindings>
    45. <MouseBinding MouseAction="LeftClick" Command="{Binding DataContext.OpenFileCommand, ElementName=mainUc}" CommandParameter="{Binding}"/>
    46. </TextBlock.InputBindings>
    47. </TextBlock>
    48. </StackPanel>
    49. </HierarchicalDataTemplate>
    50. </TreeView.ItemTemplate>
    51. </TreeView>
    52. <!-- #endregion -->



    Nicht wundern in den ViewModels, ich nutze hier noch das nuget Packet Fody, das bin ich gerade am Ändern.

    Was ich bisher gelesen habe, sollte das TreeView selbst expanden, wenn IsSelected gesetzt wird.
    Die Lösungen im Netz sind entweder CodeBehind, Behaiviors, oder Dependency Properties.
    Aber das müsste doch in WPF einfacher gehen, oder?

    Danke Euch
    "Hier könnte Ihre Werbung stehen..."
    Update:
    ich bin nun soweit gekommen, das ich im TreeView den SelectedValuePath auf SelectedItem gebunden habe

    das richtige Item wird dadurch selected (siehe Screenshot 1).
    es wird aber nicht expanded bis dahin (siehe ScreenShot 2).

    expande ich selber bis zum entsprechenden Item, sieht man das es selektiert ist.

    Änderunge die ich gemacht habe sind:

    im MainViewModel:

    C#-Quellcode

    1. public MainViewModel()
    2. {
    3. if (!string.IsNullOrEmpty(_path))
    4. {
    5. ReloadData();
    6. ClearTabPages();
    7. InitCommands();
    8. SelectedItem = new DirectoryItemViewModel(IoC.Settings.LastOpenFile, DirectoryItemTypeEnum.File);
    9. SelectedTab = new TabPageViewModel(SelectedItem.Name, SelectedItem.FullPath, new EditorViewModel(SelectedItem.FullPath));
    10. TabPages.Add(SelectedTab);
    11. }
    12. }


    und im TreeView:

    XML-Quellcode

    1. <TreeView x:Name="tv" ItemsSource="{Binding DirectoryItems}" SelectedValuePath="{Binding SelectedItem}" Grid.Row="2" BorderThickness="0" Background="{StaticResource BackgroundVeryLightBrush}">
    "Hier könnte Ihre Werbung stehen..."
    Hallo Micha

    Das problem besteht darin das du ja nicht einfach alle Ordner und dessen Childs durchlaufen und als Selected oder Expanded markieren kannst da es diese ja (noch) nicht gibt.
    Denn ich nehme an du befüllst die Childs erst beim "Expanden". Also LazyLoading. (vermute ich zumindest)

    Du musst also zusehen das du immer genau den Knoten der obersten Eben findest -> diesen Aufklappst damit die Unterodner geladen werden -> dann in diesen den nächsten "Level" suchen.

    Ich habe mal ein Demo gemacht.

    MichaHo schrieb:

    ich nutze hier noch das nuget Packet Fody, das bin ich gerade am Ändern

    Warum das plötzlich wenn ich Fragen darf.
    Wenn ich raten würde dann ist es weil es mit diesem System vermutlich keinen DesignTimeSupport gibt oder? Oder klappt das auch?

    Grüße
    Sascha
    Dateien
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hi Sascha,
    Danke Dir, schaue ich mir an.
    design time war ein Grund. Der 2. Grund ist, das man sehr schnell vergisst wo eigentlich überall benachrichtigt werden muss. Mit der Lösung die ich jetzt habe ist es nicht viel mehr Code, aber ich sehe auf Anhieb wo ich vergessen habe eine Benachrichtigung einzubauen...
    genauso wie mit den Icons, das war schon ne schöne Sache mit der Schriftart, aber es fehlten einige Icons darin die ich benötige und nun hab ich alle Icons und die Buttons verhalten sich genauso wie vorher...
    So langsam werde ich mit WPF echt warm... mein SnipsCode ist fast fertig und das nächste Projekt, diesmal beruflich, steht kurz vorm Beginn.
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    So langsam werde ich mit WPF echt warm

    Das freut mich. Man sieht bei dir auch die Entwicklung weil du eben dran bleibst. Ich weis das es für viele sehr schwer ist die WPF zu verstehen aber wenn man bedenkt was die WPF alles kann und wie mächtig Binding ist finde ich ist die WPF dann gar nicht sooo kompliziert. Anders, ja - aber nicht komplizierter.

    Schöne Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##