none
WPF TreeView SelectedItem Parent RRS feed

  • Вопрос

  • Есть примерно такой XAML

                        <TreeView Name="listHardware" ItemsSource="{Binding Path=ServerClientList}">
                            <TreeView.Resources>
                                <!-- DeviceList TEMPLATE -->
                                <DataTemplate DataType="{x:Type vm:DeviceVM}">
                                    <TextBlock Text="{Binding Path=DisplayName}" />
                                </DataTemplate>
                                <DataTemplate DataType="{x:Type vm:DeviceBaseVM}">
                                    <TextBlock Text="Добавить камеру" />
                                </DataTemplate>
                                <!-- ServerClientList TEMPLATE -->
                                <HierarchicalDataTemplate DataType="{x:Type vm:ServerClientVM}" ItemsSource="{Binding Path=DeviceList}">
                                    <Border Background="LightGray" CornerRadius="10" BorderThickness="2">
                                        <StackPanel ToolTip="{Binding Path=DisplayName}" Orientation="Horizontal">
                                            <TextBlock Margin="4" VerticalAlignment="Center" Text="{Binding Path=DisplayName}"/>
                                            <TextBlock Margin="4" VerticalAlignment="Center" Text="{Binding Path=IP, StringFormat=({0})}"/>
                                        </StackPanel>
                                    </Border>
                                </HierarchicalDataTemplate>
                                <HierarchicalDataTemplate DataType="{x:Type vm:ServerClientBaseVM}">
                                    <Border Background="LightGray" CornerRadius="10" BorderThickness="2">
                                        <StackPanel ToolTip="{Binding Path=DisplayName}" Orientation="Horizontal">
                                            <TextBlock Margin="4" VerticalAlignment="Center" Text="Добавить сервер"/>
                                        </StackPanel>
                                    </Border>
                                </HierarchicalDataTemplate>
                            </TreeView.Resources>
                        </TreeView>
    

    И ContentControl

                        <ContentControl Grid.Column="1" Content="{Binding ElementName=listBoxHardware, Path=SelectedItem}">
                            <ContentControl.Resources>
                                <DataTemplate DataType="{x:Type vm:ServerClientVM}">
                                    <controls:ServerEditControl></controls:ServerEditControl>
                                </DataTemplate>
    
                                <DataTemplate DataType="{x:Type vm:ServerClientBaseVM}">
                                    <controls:ServerAddControl></controls:ServerAddControl>
                                </DataTemplate>
    
                                <DataTemplate DataType="{x:Type vm:DeviceBaseVM}">
                                    <controls:DeviceAddControl></controls:DeviceAddControl>
                                </DataTemplate>
                            </ContentControl.Resources>
                        </ContentControl>
    

    Выглядит это примерно так:

     

    У ServerClientVM есть команда AddDeviceCommand с параметром передающим DeviceBaseVM.

    Нужно сделать что то вроде

                        <ContentControl Grid.Column="1" Content="{Binding ElementName=listBoxHardware, Path=SelectedItem}">
                            <ContentControl.Resources>
                                <DataTemplate DataType="{x:Type vm:ServerClientVM}">
                                    <controls:ServerEditControl></controls:ServerEditControl>
                                </DataTemplate>
    
                                <DataTemplate DataType="{x:Type vm:ServerClientBaseVM}">
                                    <controls:ServerAddControl></controls:ServerAddControl>
                                </DataTemplate>
    
                                <DataTemplate DataType="{x:Type vm:DeviceBaseVM}">
                                    <StackPanel>
                                        <controls:DeviceAddControl></controls:DeviceAddControl>
                                        <Button Context="Добавить" Command="{Binding тут нужно получить родительский TreeViewItem и от него AddDeviceCommand}"
    CommandParameter="{Binding тут нужно получить controls:DeviceAddControl в качестве параметра}"></Button>
                                    </StackPanel>
                                </DataTemplate>
                            </ContentControl.Resources>
                        </ContentControl>
    

     

    1 ноября 2011 г. 12:14

Ответы

  • ... Зато можно сделать так: во ViewModel создавать коллекцию, в которой последний элемент будет как раз <AddNewDeviceVM> и сделать для него отдельный шаблон, в котором будут прописаны нужные байндинги на нужные команды и не нужны никакие конвертеры, добавляющие пустой элемент. В той ветке это уже обсуждалось?


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

    Но проблема в том что по логике в DeviceList я его добавить не могу, т.к. это список устройств.

    И например код foreach не будет работать.

    foreach (DeviceVM dm in DeviceList) { _testString = _testString + dm.DisplayName; }

    Хотя сейчас я уже думаю что это вполне приемлимый вариант.

    • Предложено в качестве ответа Abolmasov Dmitry 14 ноября 2011 г. 6:21
    • Помечено в качестве ответа skysniper777 15 ноября 2011 г. 7:36
    7 ноября 2011 г. 12:48

Все ответы

  • ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.Units}"

    1 ноября 2011 г. 12:45
    Отвечающий
  • Не совсем понимаю куда это вставить и чем это поможет.

    Может не совсем ясен вопрос. Грубо говоря нужно по элементу "Добавить камеру", получить соотвествующий элемент "Server" (Server1, Server2, либо Server3).

    1 ноября 2011 г. 12:58
  • <Button Context="Добавить" 
            Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=2, AncestorType={x:Type TreeViewItem}}, Path=DataContext.AddDeviceCommand}">
    </Button>
    1 ноября 2011 г. 13:27
    Отвечающий
  • Проверил, не работает.
    1 ноября 2011 г. 14:06
  • Какую ошибку написало приложение в output -> debug?
    1 ноября 2011 г. 14:31
    Отвечающий
  • <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="200" Width="500">
        <Window.Resources>
            <Style TargetType="Rectangle">
                <Setter Property="Fill" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}, Mode=FindAncestor, AncestorLevel=2}, Path=Background}" />
            </Style>
        </Window.Resources>
        <Grid Background="Red">
            <Grid Margin="10" Background="Blue">
                <Rectangle Width="100" Height="100" />
            </Grid>
        </Grid>
    </Window>
    
    Вот этот код работает. Проверять на дереве сложнее, поэтому не проверял. Тут главное - это проставить нужный AncestorType и выставить AncestorLevel = 2, означающий, что нужно брать второго парента.
    1 ноября 2011 г. 14:38
    Отвечающий
  • У меня пишет ошибку:

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TreeViewItem', AncestorLevel='2''. BindingExpression:Path=DataContext.AddDeviceCommand; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

    Похоже все из за того что я использую <ContentControl Grid.Column="1" Content="{Binding ElementName=listBoxHardware, Path=SelectedItem}">

    Если добавить Button в самом TreeView то все работает

                                <DataTemplate DataType="{x:Type vm:DeviceBaseVM}">
                                    <StackPanel>
                                        <TextBlock Text="Добавить камеру" />
                                        <Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=2, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
                                                Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=2, AncestorType={x:Type TreeViewItem}}, Path=DataContext.AddDeviceCommand}"
                                                />
                                    </StackPanel>
                                </DataTemplate>
    
    

    Что можно сделать?


    • Изменено skysniper777 2 ноября 2011 г. 9:26
    2 ноября 2011 г. 9:25
  • Так сказать сложно где именно ошибка. Попробуйте воспользоваться Snoop-ом, в нем можно просматривать текущие значения полей и в частности DataContext. Или воспользоваться вот таким конвертером: http://www.wpftutorial.net/DebugDataBinding.html
    2 ноября 2011 г. 11:30
    Отвечающий
  • Мне кажется это не ошибка, т.к. возможно RelativeSource пляшет от ContentControl  у котрого естественно нет TreeViewItem.

    2 ноября 2011 г. 14:06
  • RelativeSource пляшет от Button-а (всмысле он пляшет от того элемента, в котором используется, в вашем случае это кнопка). Если не верите, можете всегда воспользоваться {RelativeSource Self}.
    2 ноября 2011 г. 14:24
    Отвечающий
  • Все верно от Button, но который лежит в DataTemplate ContentControl, как я думаю в этом случае он не может наити TreeViewItem

    Если Button положить в DataTemplate TreeView , то все работает, но мне нужно чтобы работало в ContentControl.

    3 ноября 2011 г. 8:23
  • Можете попробовать использовать ContentPresenter, а ресурсы вынести на уровень выше.
    3 ноября 2011 г. 12:08
    Отвечающий
  • Не уловил мысль :(

    Ресурсы на уровень выше вынести не могу, т.к. нужно чтобы в TreeView был один вид, в ContentControl другой.

    • Изменено skysniper777 3 ноября 2011 г. 12:45
    3 ноября 2011 г. 12:16
  • Есть еще вариант решения, на самом деле у меня используется конвертер для добавления последнего элемента в TreeView:

        public class WrapperConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                if (value is ObservableCollection<ServerClientBaseVM>)
                {
                    ServerClient sc = new ServerClient();
                    ServerClientBaseVM scm = new ServerClientBaseVM(sc);
                    return (value is INotifyCollectionChanged)
                        ? new CollectionExtention<ServerClientBaseVM>(value as INotifyCollectionChanged, scm)
                        : value;
                }
                if (value is ObservableCollection<DeviceBaseVM>)
                {
                    DeviceBase d = new DeviceBase();
                    DeviceBaseVM dm = new DeviceBaseVM(d);
                    return (value is INotifyCollectionChanged)
                        ? new CollectionExtention<DeviceBaseVM>(value as INotifyCollectionChanged, dm)
                        : value;
                }
                return value;
            }
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    
        class CollectionExtention<T> : IEnumerable, INotifyCollectionChanged
        {
            public event NotifyCollectionChangedEventHandler CollectionChanged;
            private IEnumerable Head;
            private IEnumerable Tail;
            public CollectionExtention(INotifyCollectionChanged col, params T[] parr)
            {
                col.CollectionChanged += (s, e) =>
                {
                    var eh = this.CollectionChanged;
                    if (eh != null) eh(this, e);
                };
                this.Head = (IEnumerable)col;
                this.Tail = parr;
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return Enumerable.Concat(this.Head.OfType<object>(), this.Tail.OfType<object>()).GetEnumerator();
            }
        }
    


    Я могу изменить класс DeviceBaseVM добавив в него ссылку на ServerClientVM которую передавать в конструкторе и в дальнейшем брать команду AddDeviceCommand от этого ServerClientVM.

    Но тут почти та же поблема - как в конвертере вот в этом месте получать сотвествующий ServerClientVM?

                if (value is ObservableCollection<DeviceBaseVM>)
                {
                    serverclient = ???
                    DeviceBase d = new DeviceBase();
                    DeviceBaseVM dm = new DeviceBaseVM(d, serverclient );
                    return (value is INotifyCollectionChanged)
                        ? new CollectionExtention<DeviceBaseVM>(value as INotifyCollectionChanged, dm)
                        : value;
                }
    

     

    3 ноября 2011 г. 12:56
  • Замените ContentControl на ContentPresenter. Остальное оставьте таким же.
    3 ноября 2011 г. 12:58
    Отвечающий
  • А зачем вы пошли через конвертер? Вы байндите View на Model? Если да, то это не гуд с точки зрения MVVM. У вас с View должна работать ViewModel и в идеале View вообще не должна знать о модели ничего. Да, некоторые вещи можно так сказать упрощать, чтобы отобразить какие-то поля модели во View (чтобы например не дублировать кучу полей из  Model во ViewModel).

    В вашем случае нужно просто создавать коллекцию ServerClientVM. У них сделать свойство вроде Devices и в нем уже создавать коллекцию камер. Забайндить все это на View и будет вам счастье без всяких враперов и конвертеров. У вас всегда будет возможность создать камеру с нужным сервером. А дальше уже крутите как хотите. Можно передавать сервер в конструктор, можно кидать событие из девайса на сервер, можно ещё кучу вещей нафантазировать.

    7 ноября 2011 г. 7:21
    Отвечающий
  • А зачем вы пошли через конвертер? Вы байндите View на Model? Если да, то это не гуд с точки зрения MVVM. У вас с View должна работать ViewModel и в идеале View вообще не должна знать о модели ничего. Да, некоторые вещи можно так сказать упрощать, чтобы отобразить какие-то поля модели во View (чтобы например не дублировать кучу полей из  Model во ViewModel).

    В вашем случае нужно просто создавать коллекцию ServerClientVM. У них сделать свойство вроде Devices и в нем уже создавать коллекцию камер. Забайндить все это на View и будет вам счастье без всяких враперов и конвертеров. У вас всегда будет возможность создать камеру с нужным сервером. А дальше уже крутите как хотите. Можно передавать сервер в конструктор, можно кидать событие из девайса на сервер, можно ещё кучу вещей нафантазировать.


    К конвертеру я пришел в этой теме http://social.msdn.microsoft.com/Forums/ru-RU/fordesktopru/thread/028687df-cfe1-421f-89f5-33342e8dc8f7 .

    View на Model я не байндю :) 

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

    Замена ContentControl на ContentPresenter ничего не дала.

    7 ноября 2011 г. 10:43
  • Хм, тогда да, именно так сделать не получится. Зато можно сделать так: во ViewModel создавать коллекцию, в которой последний элемент будет как раз <AddNewDeviceVM> и сделать для него отдельный шаблон, в котором будут прописаны нужные байндинги на нужные команды и не нужны никакие конвертеры, добавляющие пустой элемент. В той ветке это уже обсуждалось?
    7 ноября 2011 г. 11:49
    Отвечающий
  • ... Зато можно сделать так: во ViewModel создавать коллекцию, в которой последний элемент будет как раз <AddNewDeviceVM> и сделать для него отдельный шаблон, в котором будут прописаны нужные байндинги на нужные команды и не нужны никакие конвертеры, добавляющие пустой элемент. В той ветке это уже обсуждалось?


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

    Но проблема в том что по логике в DeviceList я его добавить не могу, т.к. это список устройств.

    И например код foreach не будет работать.

    foreach (DeviceVM dm in DeviceList) { _testString = _testString + dm.DisplayName; }

    Хотя сейчас я уже думаю что это вполне приемлимый вариант.

    • Предложено в качестве ответа Abolmasov Dmitry 14 ноября 2011 г. 6:21
    • Помечено в качестве ответа skysniper777 15 ноября 2011 г. 7:36
    7 ноября 2011 г. 12:48
  • Всегда можно добавить свойство, которое будет говорить, что такой элемент не нужно учитывать или брать только конкретного типа элементы. Ничего в этом криминального на первый взгляд нет, это ведь внутренний функционал ViewModel.
    7 ноября 2011 г. 17:52
    Отвечающий
  • Уважаемый пользователь, пожалуйста, не забывайте отмечать ответы. Если какое-либо сообщение или сообщения решают вашу проблему (даже если это ваше сообщение), то отметьте их как ответ. Для этого под каждым сообщением есть кнопка 'Пометить как ответ'. Спасибо.
    Для связи [mail]
    14 ноября 2011 г. 6:21