Как сохранить состояние горизонтально прокрутки?
Использую 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 шт):
Вся ваша проблема сейчас заключается в том, что вы не умеете правильно работать с WPF, из-за чего вы делаете уйму лишних телодвижений. У WPF обязательно надо знать и понимать 2 основных механизма, а именно XAML и Привязки (Binding
), если вы ими пренебрегаете, то вы получаете головную боль, которая не очень хорошо скажется на вашем проекте. В вашем проекте вообще не должно быть x:Name
, как и не должно быть обращения по имени к UI элементу через код, запомните это правило, оно очень важное!
Сейчас вы делаете
tw_tree.ItemsSource = null;
tw_tree.ItemsSource = newSource;
Что заставляет WPF перерисовать полностью весь TreeView
, подстроив его под новые данные, когда он мог просто дополнить существующее представление новым. Из-за этого у вас и возникают проблемы с выделением, ведь вы выделаете объект, который потом удаляете, после чего опять добавляете.
Теперь давайте посмотрим на то, что у вас должно быть:
Класс "ноды", который будет содержать в себе все данные конкретной ноды. В моем случае он будет содержать просто имя и коллекция внутренних нод:
public class Node(string name) { public string Name { get; set; } = name; public ObservableCollection<Node> Nodes { get; set; } = []; }
Далее создаем класс, который будет источником данных для приложения и устанавливаем его как
DataContext
главному окну. У меня это будет классMainViewModel
, соответственноDataContext = new MainViewModel();
.В классе источника создаем публичное свойство (для привязки это важно!), которое будет содержать уже список главных нод.
public ObservableCollection<Node> Nodes { get; } = [];
Обратите внимание, я везде использую
ObservableCollection
- все дело в том, что при привязках, UI надо оповещать о то, что данные обновились. За это оповещение отвечают такие интерфейсы какINotifyPropertyChanged
(если меняется значение свойства) иINotifyCollectionChanged
(если меняются данные внутри коллекции). Свойства у нас тут значения менять не будут, а вот в коллекции мы будем добавлять новые значения, поэтому нам нужна коллекция с этим интерфейсом, ну а в C# существует 2 коллекции, которые уже реализуют его (ObsevableCollection
иBindingList
), вот одну из них мы и используем.В коде я также создам генерацию нодов, у вас будет соответственно свое заполнение. Тут обратите внимание, мы не делаем
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); }
Остался 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
) и нужный вид ноде.
Запускаем, смотрим результат:
Как видите, у меня выделение не смещается, хотя я кликаю "добавить", где каждый клик добавляет новую ноду (очень хорошо видно по полосе прокрутки), в конце даже в Node 2
(раскрытую ноду) добавилась новая, и как видите, с выделением все в порядке. Единственное смещение будет тогда, когда мы сверху добавим множество новых объектов, из-за чего сам список сдвинется вниз, но даже в таком случае, с выделением будет все хорошо, оно останется. Собственно вот вам правильный подход к работе с WPF, который избавит вас от лишнего кода вроде того, что вы пишете сейчас. Удачи в изучении!