Как сохранить состояние горизонтально прокрутки?

Использую RequestBringIntoView для того чтобы не сдвигалось дерево при выборе элемента.

<EventSetter Event="FrameworkElement.RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView"/>



private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
        {

            // Ignore re-entrant calls
            if (mSuppressRequestBringIntoView)
                return;

            // Cancel the current scroll attempt
            e.Handled = true;

            // Call BringIntoView using a rectangle that extends into "negative space" to the left of our
            // actual control. This allows the vertical scrolling behaviour to operate without adversely
            // affecting the current horizontal scroll position.
            mSuppressRequestBringIntoView = true;

            TreeViewItem tvi = sender as TreeViewItem;
            if (tvi != null)
            {
                Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, tvi.ActualHeight);
                tvi.BringIntoView(newTargetRect);
            }

            mSuppressRequestBringIntoView = false;
        }

Но теперь при добавлении элемента сбрасывается положение горизонтальной прокрутки.

Как-то здесь нужно получить HorizontalOffset

tw_tree.ItemsSource = null;

tw_tree.ItemsSource = newSource;

Здесь восстановить HorizontalOffset

scrollViewer.ScrollToHorizontalOffset(ScrollHorizontalOffset)

Или в XAML добавить name scrollViewer. Но куда добавить ScrollViewer не знаю.

XAML:

<TreeView x:Name="tw_tree"  >

<TreeView.ItemContainerStyle>


    <Style TargetType="{x:Type TreeViewItem}" >


        <Style.Resources>

           ...
        </Style.Resources>

        ...

        <Style.Triggers>

           ...

            </Trigger>
        </Style.Triggers>
    </Style>
</TreeView.ItemContainerStyle>


<TreeView.ItemTemplate>

    <HierarchicalDataTemplate   >
        <!--ItemsSource="{Binding collection_node}-->

        <StackPanel Orientation="Horizontal">                                            

            <TextBlock Text="{Binding LinkName}"  MinWidth="64">

                <TextBlock.ContextMenu >

                    ...
                    </ContextMenu>
                </TextBlock.ContextMenu>
            </TextBlock>
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Ответы (1 шт):

Автор решения: EvgeniyZ

Вся ваша проблема сейчас заключается в том, что вы не умеете правильно работать с WPF, из-за чего вы делаете уйму лишних телодвижений. У WPF обязательно надо знать и понимать 2 основных механизма, а именно XAML и Привязки (Binding), если вы ими пренебрегаете, то вы получаете головную боль, которая не очень хорошо скажется на вашем проекте. В вашем проекте вообще не должно быть x:Name, как и не должно быть обращения по имени к UI элементу через код, запомните это правило, оно очень важное!

Сейчас вы делаете

tw_tree.ItemsSource = null;
tw_tree.ItemsSource = newSource;

Что заставляет WPF перерисовать полностью весь TreeView, подстроив его под новые данные, когда он мог просто дополнить существующее представление новым. Из-за этого у вас и возникают проблемы с выделением, ведь вы выделаете объект, который потом удаляете, после чего опять добавляете.

Теперь давайте посмотрим на то, что у вас должно быть:

  1. Класс "ноды", который будет содержать в себе все данные конкретной ноды. В моем случае он будет содержать просто имя и коллекция внутренних нод:

    public class Node(string name)
    {
        public string Name { get; set; } = name;
        public ObservableCollection<Node> Nodes { get; set; } = [];
    }
    
  2. Далее создаем класс, который будет источником данных для приложения и устанавливаем его как DataContext главному окну. У меня это будет класс MainViewModel, соответственно DataContext = new MainViewModel();.

  3. В классе источника создаем публичное свойство (для привязки это важно!), которое будет содержать уже список главных нод.

    public ObservableCollection<Node> Nodes { get; } = [];
    

    Обратите внимание, я везде использую ObservableCollection - все дело в том, что при привязках, UI надо оповещать о то, что данные обновились. За это оповещение отвечают такие интерфейсы как INotifyPropertyChanged (если меняется значение свойства) и INotifyCollectionChanged (если меняются данные внутри коллекции). Свойства у нас тут значения менять не будут, а вот в коллекции мы будем добавлять новые значения, поэтому нам нужна коллекция с этим интерфейсом, ну а в C# существует 2 коллекции, которые уже реализуют его (ObsevableCollection и BindingList), вот одну из них мы и используем.

  4. В коде я также создам генерацию нодов, у вас будет соответственно свое заполнение. Тут обратите внимание, мы не делаем Nodes = new ..(), мы просто добавляем/удаляем/меняем значения.

    private void Generate()
    {
        for (int i = 0; i < 10; i++)
        {
            var node = new Node($"Node {i}");
    
            if (Random.Shared.Next(0, 2) is 1)
            {
                var nodeCount = Random.Shared.Next(1, 10);
    
                for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
                {
                    var childNode = new Node($"Child Node {nodeIndex}");
                    node.Nodes.Add(childNode);
                }
            }
    
            Nodes.Add(node);
        }
    }
    

    Ну и также я создам команду для добавления в случайное место новой ноды (опять, вам это не понадобиться скорей всего, пишу просто чтоб знали, что я не делаю ничего "магического", просто добавляю в коллекцию новые объекты).

    [RelayCommand]
    private void AddNode()
    {
        var newNode = new Node("New Node");
    
        var node = Nodes[Random.Shared.Next(0, Nodes.Count)];
        node.Nodes.Add(newNode);
    
        Nodes.Add(newNode);
    }
    
  5. Остался XAML, он должен быть примерно следующим:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TreeView ItemsSource="{Binding Nodes}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
                    <TextBlock Text="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    
        <Button
            Grid.Row="1"
            Command="{Binding AddNodeCommand}"
            Content="Добавить" />
    </Grid>
    

    Обратите внимание тут на ItemsSource - он задается в XAML, а не в коде, без каких либо x:Name. Также обратите внимание на HierarchicalDataTemplate - так задается вложенность (мы привязываемся к внутреннему свойству Nodes) и нужный вид ноде.

Запускаем, смотрим результат:

Result

Как видите, у меня выделение не смещается, хотя я кликаю "добавить", где каждый клик добавляет новую ноду (очень хорошо видно по полосе прокрутки), в конце даже в Node 2 (раскрытую ноду) добавилась новая, и как видите, с выделением все в порядке. Единственное смещение будет тогда, когда мы сверху добавим множество новых объектов, из-за чего сам список сдвинется вниз, но даже в таком случае, с выделением будет все хорошо, оно останется. Собственно вот вам правильный подход к работе с WPF, который избавит вас от лишнего кода вроде того, что вы пишете сейчас. Удачи в изучении!

→ Ссылка