3

Во общем пытаюсь реализовать файловый експлорер, уже почти все сделал, но тут какая то непонятная ситуация у меня...

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

...
TreeViewItem node; // Node to move
TreeViewItem oldFolder = (TreeViewItem)node.Parent;
oldFolder.Items.Remove(node);
newFolder.Items.Add(node);
...

То итем теряет все байндинги и обработчики ивентов этого итема...

Вопрос: Как переместить TreeViewItem в TreeView так, что бы этот итем сохранил все свои свойства байндинги ивенты и все остальное, то есть как скопировать TreeViewItem в другой TreeViewItem?

UPDATE1: Ну может быть совсем уж все не теряет, но те на которые подписываюсь я - теряет... Вот как у меня все устроено: Стиль итема

<Setter Property="HeaderTemplate">
<Setter.Value>
    <DataTemplate x:Name="HeaderTemplateKey">
        <StackPanel x:Name="PART_InnerPanel" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Center" 
                    Height="20">
            <TextBlock x:Name="HeaderText" Text="{Binding}" Margin="7,1,0,0" 
                    VerticalAlignment="Center" />
            <TextBox x:Name="PART_EditableHeader" Text="{Binding Text, ElementName=HeaderText}" 
                    Margin="2,2"
                    VerticalAlignment="Stretch"
                    VerticalContentAlignment="Center"
                    Visibility="{Binding IsEdit, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:MyTreeViewItem}},
                Converter={StaticResource boolToVisibilityConverter}, Mode=OneWay}">
            </TextBox>
        </StackPanel>
    </DataTemplate>
</Setter.Value>

Потом в классе ноды я получаю PART_EditableHeader и подписываюсь на его ивенты, вот код этого всего:

public TextBox EditableHeader;
public void MyTreeViewItem_LayoutUpdated(object sender, EventArgs e)
    {
        ContentPresenter headerContentPreseter = this.Template.FindName("PART_Header", this) as ContentPresenter;
        if (headerContentPreseter != null)
        {
                EditableHeader = this.HeaderTemplate.FindName("PART_EditableHeader", headerContentPreseter) as TextBox;
        }
        LayoutUpdated -= MyTreeViewItem_LayoutUpdated;
        OnHeaderLoaded();
    }

    void OnHeaderLoaded()
    {
        if (EditableHeader != null)
        {
            KeyboardFocusChangedEventHandler preselect = delegate
            {
                // Do work...
            };
            EditableHeader.PreviewGotKeyboardFocus += preselect;
            EditableHeader.GotKeyboardFocus += preselect;
            EditableHeader.PreviewMouseDown += (s, e) =>
            {
                // Do work...
            };
            EditableHeader.LostFocus += delegate
            {
                // Do work...
            };
            EditableHeader.PreviewMouseDoubleClick += (s, e) =>
            {
                // Do work...
            };
        }
    }

И все как бы работает нормально, до первого перемещения ноды, как только я перемещаю сразу перестают работать все обработчики описанные в методе OnHeaderLoaded(). Пока что по дебагу такое ощущение что мне нужно просто обновить еще раз EditableHeader, но почему-то когда пытаюсь обновить тем же способом то получаю ContentPresenter headerContentPreseter == null...

Короче я уже ничего не понимаю почему оно не работает... Помогите разобраться...

UPDATE2: Вот как биндится:

    var tabItem = new TabItem();
    TextBlock tb = new TextBlock();
    Binding b = new Binding
    {
        Source = node,
        Mode = BindingMode.OneWay,
        Path = new PropertyPath(TreeViewNode.HeaderProperty)
    };
    tb.SetBinding(TextBlock.TextProperty, b);

    b = new Binding
    {
        Source = node,
        Mode = BindingMode.OneWay,
        Path = new PropertyPath(TreeViewNode.PathProperty)
    };
    tabItem.SetBinding(TabItem.ToolTipTextProperty, b);
SkyDancer
  • 161
  • 2
  • 10
  • Совсем все event'ы он терять не может. Можете сообразить минимальный воспроизводящий пример? – VladD Sep 14 '15 at 18:37
  • Судя по всему, при перемещении вызывается Unloaded и потом снова Loaded. И повторно применяется Template. Я бы на вашем месте подписался на OnApplyTemplate или что-то подобное, и перечитывал EditableHeader. (Вот немного почитать близко к теме.) – VladD Sep 14 '15 at 20:31
  • @VladD, Был занят неделю, вот появилось время, ну и во общем я попробовал подписаться на OnApplyTemplate и там получаю исключение this.HeaderTemplate.FindName("PART_EditableHeader", header) threw an exception of type 'System.InvalidOperationException'... – SkyDancer Sep 22 '15 at 09:00
  • Хм, странно. А что внутри исключения? Может быть, что-то полезное найдётся? – VladD Sep 22 '15 at 09:05
  • Вроде кажись нашел решение, поменял обновление элементов с LayoutUpdated на Loaded и вроде теперь получается так: Я удаляю ноду - вызывается Unloaded, потом я добавляю эту ноду в другую ноду и вызывается Loaded этой ноды, и в этом ивенте я обновляю все элементы, и теперь работают все байндинги и все во обще вроде заработало как нужно, сейчас еще подебажу посмотрю может где то еще будет баг, но пока что выглядит как будто все нормльно работает. Почему не работало при OnApplyTemplate я так и не понял, в исключении ничего нету.. – SkyDancer Sep 22 '15 at 09:29
  • @VladD, Все таки нашел баг один, у ноды есть Dependency property - Path и к нему байндится TextBlock.TextProperty в моей программе, но когда нода удаляется(теряет родителя) то этот байндинг слетает и больше не работает... Но когда я перемещаю как бы файлы вместе с папкой, то есть перемещаю ноду-родителя полностью, вместе с вложенными нодами, то тогда все работает нормально, и байндинги не слетают... – SkyDancer Sep 22 '15 at 11:14
  • А как именно биндится? Покажите код. – VladD Sep 22 '15 at 12:35
  • А, тогда понятно, в чём причина. Вы ведь пишете: Source = node, видите? При переносе элемента, понятно, node сам по себе не изменится. Так что он всё ещё ссылается на старый, «мёртвый» экземпляр node. Вам нужно обновить его вручную, раз вы переставляете его таким образом. – VladD Sep 22 '15 at 14:17
  • Я не совсем понял, а как именно мне обновить? Вручную забиндить еще раз? – SkyDancer Sep 22 '15 at 15:58
  • Именно так. У вас в этот момент должно быть правильное обновлённое значение node. – VladD Sep 22 '15 at 15:59
  • Жаль что нету другого способа, как то сохранить все байндинги перед удалением ноды и потом восстановить их обратно, ибо теряется весь смысл байндинга, если мне нужно вручную перебайндить то с таким же успехом я могу хранить мой объект как свойство ноды, и вручную менять свойства этого объекта при изминении определенных свойств ноды, или тупо в ноде создать ивент, например NodeUpdated, подписать на него метод моего объекта и уже там все поменять передав ноду аргументом этого ивента. – SkyDancer Sep 22 '15 at 16:39
  • Ну, это да. Для начала проверьте, чтобы так работало. А потом можно, по идее, попробовать написать байндинг по-другому, чтобы он обновлялся автоматически. – VladD Sep 22 '15 at 16:40

2 Answers2

1

итем теряет все байндинги и обработчики ивентов этого итема...

Не надо менять UI вручную. В обработчике мыши надо получить контейнер, извлечь из него элемент данных, после этого вызвать DoDragDrop, и если не было отмены сброса, удалить элемент из источника данных.
Пример переноса изображения между двумя контролами в WPF - тут.

Stack
  • 9,452
  • DoDragDrop? Как-то сильно сложно, мы ж таскаем контрол внутри своего же приложения, а не данные из другого приложения. Вот тут вроде попроще. – VladD Dec 23 '15 at 22:28
  • @VladD "DoDragDrop? Как-то сильно сложно, мы ж таскаем контрол внутри своего же приложения" -- а что сложно? экспортировать и обращаться к WinAPI не надо. вся логика в одном методе *_PreviewMouseMove. метод небольшой, и его можно использовать в разных ситуациях. – Stack Dec 23 '15 at 22:44
  • Код получается сложнее, и делать приходится много лишнего. Например, приходится оформлять format, который по сути не нужен. – VladD Dec 23 '15 at 22:52
  • @VladD "делать приходится много лишнего. ... приходится оформлять format, который по сути не нужен" -- необязательно. format можно использовать стандартный. зависит от ситуации. а насчет лишнего - попробуйте написать программу, которая позволяет перетаскивать данные между разными контролами, а также поддерживает сброс данных извне. – Stack Dec 23 '15 at 23:05
  • Если нужен дроп из других приложений — то да. А если просто перетаскивание контролов внутри приложения, то пример из первого комментария концептуально вроде проще. – VladD Dec 23 '15 at 23:08
0

Вынесено из комментариев:

Причина в коде Source = node. При переносе элемента, понятно, node сам по себе не изменится. Так что он всё ещё ссылается на старый, «мёртвый» экземпляр node. Вам нужно обновить его вручную, раз вы переставляете его таким образом.

Ещё лучше было бы вовсе отказаться от ручного Binding'а, перенести его в XAML, а добавлением/удалением элементов заниматься на уровне VM: изменение коллекции VM-элементов в folder'е должно автоматически приводить к добавлению/удалению UI-элементов.

VladD
  • 206,799