Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273

Отложенная загрузка ветвей TreeView через команды в MVVM

09.04.2025, 14:42. Показов 3679. Ответов 32

Студворк — интернет-сервис помощи студентам
Здравствуйте, помогите, пожалуйста, разобраться. Как сделать отложенную загрузку ветвей в TreeView.

Есть следующие классы сущностей (на названия не обращайте внимание изменены специально):

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class GeneralObject
{
    public int Id { get; set; }
    public string Product { get; set; }
    public BitmapSource IconType { get; set; }
    public BitmapSource IconState { get; set; }
}
 
public class Materials : GeneralObject
{
    public string Number { get; set; }
    public List<TypeFolder> TypeFolders { get; set; }
}
 
public class TypeFolder : GeneralObject
{
    public List<Shop> Shops { get; set; }
}
 
public class Shop : GeneralObject
{
    public string Code { get; set; }
    public string Site { get; set; }
 
    public List<Something> Somethings { get; set; }
}
 
public class Something : GeneralObject
{
    public byte[] File { get; set; }
    public Shop Parent { get; set; }
}
Вот так дерево сделано в XAML (лучшего способа придумать не смог)
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<TreeView Grid.Row="1"
          x:Name="tree"
          BorderBrush="Black"
          BorderThickness="0.5"
          Width="Auto"
          Margin="3"
          ItemsSource="{Binding Materials}">
 
    <TreeView.Resources>
        <DataTemplate x:Key="BaseItemTemplate">
            <StackPanel Orientation="Horizontal"
                        VerticalAlignment="Center">
 
                <Image Source="{Binding IconType}"
                       Height="16"
                       Width="16"
                       Margin="3,0,0,0"/>
 
                <Image Source="{Binding IconState}"
                       Height="16"
                       Width="16"
                       Margin="3,0,3,0"/>
 
                <TextBlock Text="{Binding Product}"
                           FontSize="14"/>
            </StackPanel>
        </DataTemplate>
 
        <HierarchicalDataTemplate DataType="{x:Type model:Materials}"
                                  ItemsSource="{Binding TypeFolders}">
            <ContentPresenter Content="{Binding}" 
                              ContentTemplate="{StaticResource BaseItemTemplate}"/>
        </HierarchicalDataTemplate>
 
        <HierarchicalDataTemplate DataType="{x:Type model:TypeFolder}"
                                  ItemsSource="{Binding Shops}">
            <ContentPresenter Content="{Binding}" 
                              ContentTemplate="{StaticResource BaseItemTemplate}"/>
        </HierarchicalDataTemplate>
 
        <HierarchicalDataTemplate DataType="{x:Type model:Shop}"
                                  ItemsSource="{Binding Somethings }">
            <ContentPresenter Content="{Binding}" 
                              ContentTemplate="{StaticResource BaseItemTemplate}"/>
        </HierarchicalDataTemplate>
 
        <DataTemplate DataType="{x:Type model:Something}">
            <ContentPresenter Content="{Binding}"
                              ContentTemplate="{StaticResource BaseItemTemplate}"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>
В Shop список объектов Something грузится очень долго объектов много и тд и тп. и начальная инициализация окна и заполнение дерева растягивается на минуты. Необходимо сделать так, чтобы при попытке раскрыть ветвь Shop происходила загрузка списка объектов только для этой ветки. Как это можно реализовать?

пробовал через тригеры привязаться к команде, но не получается получить именно TreeViewItem, чтобы взять из него необходимые данные
XML
1
2
3
4
5
6
<b:Interaction.Triggers>
    <b:EventTrigger EventName="Expanded" SourceObject="OriginalSource"> 
        <b:InvokeCommandAction Command="{Binding LoadSomething}"
                               CommandParameter="{Binding ElementName=tree, Path=SelectedItem}"/>
    </b:EventTrigger>
</b:Interaction.Triggers>
и такое пробовал SourceObject="{x:Type TreeViewItem}"

Нужно не только чтоб по событию вызывалась команда но еще и получить объект сам TreeViewItem чтобы по его Id загрузить дочерние элементы

Есть еще вопрос по поводу съедает ли BitmapSource качество изображения, когда его создают из Bitmap?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
09.04.2025, 14:42
Ответы с готовыми решениями:

WPF команды и MVVM. Часть 2. Всплытие команд. Реализация команды для списка элементов [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html На практике часто...

Перебор всех ветвей в treeView
Имеется элемент treeView с кучей ветвей, у каждой ветки есть дочерние, у тех в свою очередь тоже...

Вывод всех ветвей TreeView в ComboBox
Здравствуйте! Есть treeview в которое пользователь добавляет свои узлы и ветви, нужно что бы они...

32
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273
10.04.2025, 10:43  [ТС]
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Это без проблем можно сделать в Code Behind или Behavior
правильно понимаю, что вы предлагаете либо в классе окна добавить обработчик события и вызывать метод VM, который лежит в контексте? либо создать класс унаследованный от Behavior?

А как в самом XAML указать, что есть какой-то обработчик для конкретной ветки HierarchicalDataTemplate ?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
10.04.2025, 13:14
Добавлено через 4 минуты
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
правильно понимаю, что вы предлагаете либо в классе окна добавить обработчик события и вызывать метод VM, который лежит в контексте? либо создать класс унаследованный от Behavior?
Это из того что первым пришло в голову.
Как минимум стоит проверить сначала в Code Behind точно ли в этом проблема. А уже потом подумать как "красиво" это оформить.
Но возможно в InvokeCommandAction даже есть возможность подписки на обработанные события. Я их редко использую и не могу точно сказать.
Может можно сделать свой кастомный класс производный от InvokeCommandAction для подписки на обработанные события.
0
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273
10.04.2025, 13:38  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Как минимум стоит проверить сначала в Code Behind точно ли в этом проблема
у меня в xaml нет TreeView.ItemTemplate, я не так отображение настраиваю, как мне обратиться к событию Expanded?
Изображения
  
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
10.04.2025, 16:16
Обсуждение качества отображения иконок перенесено в тему Конвертация Bitmap в ImageSource через массив байт делает изображение размытым
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
10.04.2025, 16:46
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
как мне обратиться к событию Expanded?
Проще всего собрать все в одном месте - в самом TreeView
Создать наследник и вывести наружу все события через команды.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
   public class xTreeView : TreeView
   {
       public xTreeView()
       {
           var itemStyle = new Style( typeof( TreeViewItem ) );
           itemStyle.Setters.Add( new EventSetter( TreeViewItem.LoadedEvent, new RoutedEventHandler( ItemLoaded ) ) );
           itemStyle.Setters.Add( new EventSetter( TreeViewItem.UnloadedEvent, new RoutedEventHandler( ItemUnloaded ) ) );
           itemStyle.Setters.Add( new EventSetter( TreeViewItem.MouseDoubleClickEvent, new MouseButtonEventHandler( ItemDoubleClick ) ) );
 
           ItemContainerStyle = itemStyle;
       }
       void ItemLoaded( object sender, RoutedEventArgs e )
       {
           if( sender is TreeViewItem item )
           {
               item.Expanded += itemExpanded;
               item.Collapsed += itemCollapsed;
           }
       }
       void ItemUnloaded( object sender, RoutedEventArgs e )
       {
           if( sender is TreeViewItem item )
           {
               item.Expanded -= itemExpanded;
               item.Collapsed -= itemCollapsed;
           }
       }
       void itemExpanded( object sender, RoutedEventArgs e )
       {
           if( sender is TreeViewItem item )
               if( ExpandCommand?.CanExecute( item.DataContext ) == true )
                   ExpandCommand.Execute( item.DataContext );
           e.Handled = true;
       }
       void itemCollapsed( object sender, RoutedEventArgs e )
       {
           if( sender is TreeViewItem item )
               if( CollapseCommand?.CanExecute( item.DataContext ) == true )
                   CollapseCommand.Execute( item.DataContext );
           e.Handled = true;
       }
       void ItemDoubleClick( object sender, RoutedEventArgs e )
       {
           // create command and execute from here           
       }
 
       public RelayCommand ExpandCommand { get { return ( RelayCommand ) GetValue( ExpandCommandProperty ); } set { SetValue( ExpandCommandProperty, value ); } }
       public static readonly DependencyProperty ExpandCommandProperty = DependencyProperty.Register( "ExpandCommand", typeof( RelayCommand ), typeof( xTreeView ), new PropertyMetadata( null ) );
 
       public RelayCommand CollapseCommand { get { return ( RelayCommand ) GetValue( CollapseCommandProperty ); } set { SetValue( CollapseCommandProperty, value ); } }
       public static readonly DependencyProperty CollapseCommandProperty = DependencyProperty.Register( "CollapseCommand", typeof( RelayCommand ), typeof( xTreeView ), new PropertyMetadata( null ) );
   }

Создать в VM команды ExpandComm и CollapseComm
и привязать к xTreeView
XML
1
    <local:xTreeView ItemsSource="{Binding Drives}" ExpandCommand="{Binding ExpandComm}" CollapseCommand="{Binding CollapseComm}"/>
в VM будут обработчики команды
C#
1
2
3
4
5
6
7
   void ExpandExe( object obj )
   {
       if( obj is Item item ) // твои типы
       {
       
       }           
   }
0
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273
15.04.2025, 16:47  [ТС]
Решил попробовать через Behavior
сделал вот такой класс, чтобы добавить поведение для обработки события и выполнения команды:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TreeViewItemExpandedBehavior : Behavior<TreeViewItem>
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(TreeViewItemExpandedBehavior));
 
    public ICommand Command
    {
        get
        {
            return (ICommand)GetValue(CommandProperty);
        }
        set
        {
            SetValue(CommandProperty, value);
        }
    }
 
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Expanded += OnTreeViewItemExpanded;
    }
 
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Expanded -= OnTreeViewItemExpanded;
    }
 
    private void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
    {
        if (Command != null && Command.CanExecute(AssociatedObject.DataContext))
            Command.Execute(AssociatedObject.DataContext);
    }
}
перестроил структуру дерева, сейчас в XAML дерево выглядит вот так:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<TreeView Grid.Row="1"
          x:Name="tree"
          BorderBrush="Black"
          BorderThickness="0.5"
          Width="Auto"
          Margin="3"
          ItemsSource="{Binding Tree}">
 
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
 
            <StackPanel Orientation="Horizontal"
                        VerticalAlignment="Center">
 
                <Image Source="{Binding CurrentObjeсt.IconType}"
                       RenderOptions.BitmapScalingMode="NearestNeighbor"
                       SnapsToDevicePixels="True"
                       Height="16"
                       Width="16"
                       Margin="3,0,0,0"/>
 
                <Image Source="{Binding CurrentObjeсt.IconState}"
                       RenderOptions.BitmapScalingMode="NearestNeighbor"
                       SnapsToDevicePixels="True"
                       Height="16"
                       Width="16"
                       Margin="3,0,3,0"/>
 
                <TextBlock Text="{Binding CurrentObjeсt.Product}"
                           FontSize="14"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
 
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            
            <Setter Property="IsExpanded"
                    Value="{Binding IsExpanded}"/>
 
            <Setter Property="ToolTip"
                    Value="{Binding ToolTip}"/>
 
            <Setter Property="myBehaviors:TreeViewItemExpandedBehavior.Command"
                    Value="{Binding DataContext.NodeExpandedCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>
Специально добавил в TreeView.ItemContainerStyle вот это :
XML
1
2
<Setter Property="myBehaviors:TreeViewItemExpandedBehavior.Command"
                    Value="{Binding DataContext.NodeExpandedCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
Но это не срабатывает, в команду не заходит при раскрытии узла. Я что-то не добавил или что-то упустил?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
15.04.2025, 17:55
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
Я что-то не добавил или что-то упустил?
На первый взгляд - всё на месте.
Но я без компа. Просто читаю здесь код.

Попробуйте для теста добавить в ItemContainerStyle EventSetter для Expanded с обработчиком в Code Behind.
И посмотрите будет он вызываться или нет.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
16.04.2025, 02:12
Лучший ответ Сообщение было отмечено IIIIIIIIIgor как решение

Решение

Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
public class TreeViewItemExpandedBehavior : Behavior<TreeViewItem>
Пример рабочей реализации:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    public class TreeViewItemExpandedBehavior : Behavior<TreeView>
    {
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(TreeViewItemExpandedBehavior));
 
        public ICommand Command
        {
            get
            {
                return (ICommand)GetValue(CommandProperty);
            }
            set
            {
                SetValue(CommandProperty, value);
            }
        }
 
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.AddHandler(TreeViewItem.ExpandedEvent, (RoutedEventHandler)OnTreeViewItemExpanded);
        }
 
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.RemoveHandler(TreeViewItem.ExpandedEvent, (RoutedEventHandler)OnTreeViewItemExpanded);
        }
 
        private void OnTreeViewItemExpanded(object sender, RoutedEventArgs e)
        {
            object? item = (e.OriginalSource as TreeViewItem)?.DataContext;
            if (Command != null && Command.CanExecute(item))
                Command.Execute(item);
        }
    }
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public class Shop : GeneralObject
    {
        public string? Code { get; set; }
        public string? Site { get; set; }
 
        public List<Something>? Somethings { get; set; }
    }
 
 
    public class ShopsViewModel : BaseInpc
    {
        private GeneralObject? _selectObject;
 
        public ShopsViewModel()
        {
            LoadSomething = new RelayCommand<object>(obj => SelectObject=obj as GeneralObject);
        }
 
        public ObservableCollection<Shop> Shops { get; } =
        [
            new Shop { Id = 1 , Code="qw", Somethings=[new Something()]},
            new Shop { Id = 2 , Code="er", Somethings=[new Something()]},
            new Shop { Id = 3 , Code="ty", Somethings=[new Something()]},
        ];
 
        public RelayCommand LoadSomething { get; }
 
        public GeneralObject? SelectObject { get => _selectObject; set => Set(ref _selectObject, value); }
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<Window x:Class="CFTopics2025.SF.IIIIIIIIIgor.Topic3203147.TreeCommandWindow"
        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:local="clr-namespace:CFTopics2025.SF.IIIIIIIIIgor.Topic3203147"
        xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="TreeCommandWindow" Height="450" Width="800"
        DataContext="{DynamicResource vm}">
    <Window.Resources>
        <local:ShopsViewModel x:Key="vm"/>
        <local:ExpandedArgsConverter x:Key="excon"/>
    </Window.Resources>
    <Grid>
        <TreeView ItemsSource="{Binding Shops}"
                  TreeViewItem.Expanded="OnItemExpanded">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Somethings}"
                                          DataType="{x:Type local:Shop}">
 
                    <StackPanel Orientation="Horizontal"
                        VerticalAlignment="Center">
                        <TextBlock Text="{Binding Id}"
                                   FontSize="14"/>
                        <TextBlock Text="{Binding Code}"
                                   FontSize="14"/>
 
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
            <b:Interaction.Behaviors>
                <local:TreeViewItemExpandedBehavior Command="{Binding LoadSomething}"/>
            </b:Interaction.Behaviors>
        </TreeView>
        <TextBlock Text="{Binding SelectObject.Id}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   FontSize="30"/>
    </Grid>
</Window>
1
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273
16.04.2025, 09:05  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Пример рабочей реализации:
Изменив свой класс TreeViewItemExpandedBehavior как у вас и в XAML вынеся добавление поведения в другое место, это дало результат, теперь команда срабатывает. Спасибо!

Есть вопросы, чтобы понять для будущего:
1 - почему стали наследоваться от Behavior<TreeView> типизированным TreeView, а не TreeViewItem?
2 -
C#
1
AssociatedObject.AddHandler(TreeViewItem.ExpandedEvent, (RoutedEventHandler)OnTreeViewItemExpanded);
тут мы получается добавляем обработчик события для самого TreeView (если нет, то объясните, пожалуйста, как оно работает)?

И еще вопрос дерево в окне не обновляется почему то. Команда срабатывает, все загружается (в памяти видно, что новые объекты в Children добавлены), но в окне ничего не появляется и даже треугольник для раскрытия ветки пропадает.

хотя свойство Children это ObservableCollection и она должна уведомлять что она изменилась

сейчас использую такую структуру:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TreeObject
{
    public TreeObject()
    {
        Children = new ObservableCollection<TreeObject>();
    }
 
    public GeneralObject CurrentObjeсt { get; set; }
    public GeneralObject Parent { get; set; }
    public ObservableCollection<TreeObject> Children { get; set; }
    public bool IsExpanded { get; set; }
    public string ToolTip { get; set; }
}
вот так добавляю детей в коллекцию:
C#
1
2
3
4
5
6
7
8
9
10
private void OnNodeExpandedCommand(object p)
{
    var obj = p as TreeObject;
    
    if (obj.CurrentObjeсt is Shop)
    {
        obj.Children.Clear();
        obj.Children = _builder.Build(obj);
    }
}
Что я делаю снова не так?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
16.04.2025, 10:45
Лучший ответ Сообщение было отмечено IIIIIIIIIgor как решение

Решение

Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
почему стали наследоваться от Behavior<TreeView> типизированным TreeView, а не TreeViewItem?
Чтобы можно было подписаться на уровне TreeView.
Это же проще чем задавать ещё и AttachedPeoperty для подписки на уровне TreeViewItem через сеттеры стиля.

Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
тут мы получается добавляем обработчик события для самого TreeView (если нет, то объясните, пожалуйста, как оно работает)?
Прочитайте за RoutedEvent - маршрутизируемые события.
Большинство из них используют две стратегии:
"Всплывание" - это когда событие последовательно вызывается от источника во всех его визуальных предках;
"Туннелирование" - это когда событие последовательно вызывается от самого верхнего предка (обычно Окно) до элемента источника. Почти всегда такие событие именуются с префиксом "Preview".

"Обычное" CLR свойство, которое есть в экземплярах элементов, это просто оболочка для упрощения обращения к RoutedEvent.
Эта оболочка, по соглашению WPF, может отловить только необработанные RoutedEvent.
Так же такая оболочка предоставляется только RoutedEvent "нужных" этому элементу (в большинстве случаев они и объявляются в этом же элементе).
Но как я выше объяснял, через элемент проходят не только его события, но и все Всплывающие и Туннельные события всех дочерних элементов.
Для подписки на обработанные события и события дочерних элементов, надо работать напрямую с RoutedEvent, а не через CLR оболочку.

Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
хотя свойство Children это ObservableCollection и она должна уведомлять что она изменилась
У вас нет уведомления в свойстве public ObservableCollection<TreeObject> Children { get; set; }. Поэтому если вы инициализируете это свойство после создания представления TreeObject, то представление не будет обновляться.
Уберите сеттер из этого свойства чтобы избежать таких ошибок:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TreeObject
{
    // public TreeObject()
    // {
    //     Children = new ObservableCollection<TreeObject>();
    // }
 
    public GeneralObject CurrentObjeсt { get; set; }
    public GeneralObject Parent { get; set; }
    public ObservableCollection<TreeObject> Children { get; /* set; */ } = new();
    public bool IsExpanded { get; set; }
    public string ToolTip { get; set; }
}

Так же для получения иконки "Expand" нужно чтобы в коллекции Children был хоть один элемент.

Напрашивается:
C#
10
    public ObservableCollection<TreeObject> Children { get; /* set; */ } = [new TreeObject()];
Но такая реализация создаст зацикливание.

Есть различные "хитрые" способы достичь желаемого. Но эти хитрости требуют и дополнительного кода, и подгонки под конкретную задачу.
1
3 / 3 / 0
Регистрация: 23.12.2016
Сообщений: 273
16.04.2025, 11:04  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Так же для получения иконки "Expand" нужно чтобы в коллекции Children был хоть один элемент.
да, я это делаю при построении дерева в коде добавляю в коллекцию 1 пустой объект, так что иконка у меня отображается.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Уберите сеттер из этого свойства чтобы избежать таких ошибок:
Спасибо это мне помогло.
Сделал вот так:
C#
1
2
3
4
5
6
7
public ObservableCollection<TreeObject> Children { get; } = new ObservableCollection<TreeObject>();
public void AddChildren(List<TreeObject> children)
{
    Children.Clear();
 
    children.ForEach(x => Children.Add(x));
}
а при изначальном создании дерева в нужную ветку добавил пустой объект (чтобы отображался значок раскрытия) и сейчас все загружается и отображается.
Спасибо!

Цитата Сообщение от Элд Хасп Посмотреть сообщение
У вас нет уведомления в свойстве public ObservableCollection<TreeObject> Children { get; set; }. Поэтому если вы инициализируете это свойство после создания представления TreeObject, то представление не будет обновляться.
где можно про это подробнее почитать чтоб разобраться?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
16.04.2025, 12:01
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
получается мне это не поможет сейчас? и я так предполагаю что каждый раз при обращении к этому свойству он просто будет создавать новую ObservableCollection с 1 пустым объектом ?
Самый простой вариант, на мой взгляд, триггеры на ItemsSource.
Пишу здесь, поэтому воспринимайте код как концепт, а не рабочий вариант.
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    <...Resources>
        <CompositeCollection x:Key="empty">
          <любой объект/>
        </CompositeCollection>
    <...Resources>
 
 
 
             <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Somethings}"
                                          DataType="{x:Type local:Shop}">
 
                    <StackPanel Orientation="Horizontal"
                        VerticalAlignment="Center">
                        <TextBlock Text="{Binding Id}"
                                   FontSize="14"/>
                        <TextBlock Text="{Binding Code}"
                                   FontSize="14"/>
 
                    </StackPanel>
                    <HierarchicalDataTemplate.Triggers>
                        <DataTrigger Binding="{Binding Somethings.Count}" Value="0">
                            <Setter Property="ItemsSource" Value="{StaticResource empty}"/>
                        </DataTrigger>
                    </HierarchicalDataTemplate.Triggers>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
Добавлено через 2 минуты
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
а вообще тогда такое свойство становится доступным только для чтения, как мне в него потом положить дочерние объекты?
Свойство-коллекция "только для чтения" - не позволит менять в нём экземпляр коллекции.
Но изменять (добавлять, удалять, заменять) итемы в этой коллекции можно.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,176
Записей в блоге: 2
17.04.2025, 10:36
Цитата Сообщение от IIIIIIIIIgor Посмотреть сообщение
где можно про это подробнее почитать чтоб разобраться?
Прочитайте за Биндинг и INPC.
Биндинг отслеживает изменения свойств (в основном) по уведомлениям INPC.

Что такое "изменение свойства (или поля)".... ну, это самые базовые знания по Шарпу.
Для значимого типа - это присвоение нового экземпляра (значения).
Для ссылочного типа - это присвоение ссылки на другой объект (или обнуление ссылки).

Изменение свойств самих объектов (экземпляров) никак не влияет на свойство которое содержит ссылку на этот объект.

Например, вот такое свойство:
C#
1
public GeneralObject SelectObject {get => ....; set => ...;} // Свойство с уведомлением через INPC
Если будет изменено вложенное свойство SelectObject.Product = "Другой продукт", то привязка никак не узнает об этом и не обновится.

То же самое с коллекциями (а все коллекции это ссылочные типы).
Если есть свойство с коллекцией, то изменение самой коллекцией - это не изменение свойства с ссылкой на коллекцию.
Для отслеживания за изменениями коллекции в WPF используется INCC (простая его реализация это ObservableCollection) и IBL (простая его реализация - это BindingList).
В свойстве с такой коллекцией происходит "двойное" наблюдение. За изменением самого свойства (то есть записью в него новой коллекции) наблюдение происходит через INPC, а за изменением в самой коллекции наблюдение происходит через INCC или IBL.
В вашей реализации вы использовали ObservableCollection. Но это "вторая" очередь наблюдения. Так как вы изменяли само свойство в котором была ссылка на эту коллекцию (создавали новый экземпляр коллекции и присваивали свойству), то для обновления Биндинга в этом свойстве должно было быть уведомление через INPC.
Но в вашей задаче такое наблюдение избыточно. Поэтому можно просто сделать это свойство иммутабельным (только для чтения). В этом случае Биндингу достаточно один раз прочитать значение свойства и потом не наблюдать за его изменением.

Биндинг читает свойство первый раз при загрузке элемента (событие Loaded). Поэтому, необязательно делать свойство только для чтения. Достаточно обеспечить чтобы его значение не менялось после загрузки элемента. Но такой вариант может повлечь случайные ошибки (как и было в вашем случае). Поэтому его следует использовать с большой осторожностью, ясно понимая его недостатки и какие преимущества такая реализация может дать в конкретной задаче.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
17.04.2025, 10:36

Рисую линию без MVVM все ОК. C MVVM произвольный старт
Добрый день всем. Рисую линию при помощи анимации. Все получилось -рисуется, стирается. Но у...

Приложение MVVM. Быстрая "Нестрогая MVVM" реализация
В этой теме будет размещена простая и быстрая реализация. Основная тема:...

Отложенная обработка событий
Здравствуйте, предположим, у нас есть Логика и Отображение. Отображение подписано на событие...

Побочный эффект отложенной загрузки страницы (браузер перебрасывает пользователя в начало страницы)
День добрый! Имеется ASP.NET страница, для ускорения загрузки на ней используется отложенная...

Нажатие на ветвь через Treeview и вывод в dataGridView
Добрый день. Есть форма подключенная к базе данных (Access). Слева на форме Treeview , справа...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
33
Ответ Создать тему
Новые блоги и статьи
Запустил конкурс "тем и промптов для текстовых квестов созданных почти чисто ИИ"
Adler 06.10.2026
Всем привет! За последние три-четыре дня я создал более 16 текстовых квестовых игр используя преимущественно по одному запросу к ИИ на игру. Мне так понравилось смотреть все ветки/ сцены во всех. . .
ИИ не может найти нужный язык в списке
Supersumestria 05.10.2026
Я ему даю вот такое изображение и прошу найти и подчеркнуть немецкий язык. Возвращает он вот это: https:/ / i. **********/ vqBWLe2. png Нужную строчку в 3й колонке просто выдумал. . Это. . .
Новая последняя моя музыка в SUNO
zorxor 05.10.2026
Здравствуйте, дорогие мои друзья! С большой радостью я хотел бы представить вам свою новую последнею музыку, которую сгенерировала мне по моей просьбе нейросеть SUNO. С уважением, zorxor. Это. . .
Программный домашний кинотеатр
russiannick 27.09.2026
Сподобился на программный домашний кинотеатр. В качестве ЯВУ по традиции выбрал js. В помощники взял Яндекс-Алису. Было создано три зала на разные интересы. исторические и ретро сериал Хичкок. . .
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru