WPF MVVM TreeView SelectedItem - PullRequest
       5

WPF MVVM TreeView SelectedItem

30 голосов
/ 23 августа 2011

Это не может быть так сложно.TreeView в WPF не позволяет вам устанавливать SelectedItem, говоря, что это свойство ReadOnly.У меня есть TreeView, заполняющий, даже обновляющий, когда изменяется коллекция данных.

Мне просто нужно знать, какой элемент выбран.Я использую MVVM, поэтому нет кода или переменной, на которую можно ссылаться в виде дерева. Это единственное решение Я нашел, но это очевидный хак, он создает еще один элемент в XAML, который использует привязку ElementName для установки себя на выбранный элемент древовидных представлений, который вы также должны затем связать с вашей моделью представления. Несколько других вопросов задаются по этому поводу, но других рабочих решений не дано.

Я видел этот вопрос , но с использованием ответаданное дает мне ошибки компиляции, по какой-то причине я не могу добавить ссылку на blend sdk System.Windows.Interactivity в мой проект.Там написано «неизвестная ошибка system.windows не была предварительно загружена», и я еще не выяснил, как обойти это.

Для бонусных баллов: какого черта Microsoft сделала свойство SelectedItem этого элемента ReadOnly?

Ответы [ 6 ]

47 голосов
/ 23 августа 2011

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

Эскиз:

<TreeView ItemsSource="{Binding TreeData}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
    private static object _selectedItem = null;
    // This is public get-only here but you could implement a public setter which
    // also selects the item.
    // Also this should be moved to an instance property on a VM for the whole tree, 
    // otherwise there will be conflicts for more than one tree.
    public static object SelectedItem
    {
        get { return _selectedItem; }
        private set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                OnSelectedItemChanged();
            }
        }
    }

    static virtual void OnSelectedItemChanged()
    {
        // Raise event / do other things
    }

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_isSelected != value)
            {
                _isSelected = value;
                OnPropertyChanged("IsSelected");
                if (_isSelected)
                {
                    SelectedItem = this;
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
12 голосов
/ 23 августа 2011

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

public class TreeViewHelper
{
    private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();

    public static object GetSelectedItem(DependencyObject obj)
    {
        return (object)obj.GetValue(SelectedItemProperty);
    }

    public static void SetSelectedItem(DependencyObject obj, object value)
    {
        obj.SetValue(SelectedItemProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));

    private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is TreeView))
            return;

        if (!behaviors.ContainsKey(obj))
            behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));

        TreeViewSelectedItemBehavior view = behaviors[obj];
        view.ChangeSelectedItem(e.NewValue);
    }

    private class TreeViewSelectedItemBehavior
    {
        TreeView view;
        public TreeViewSelectedItemBehavior(TreeView view)
        {
            this.view = view;
            view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
        }

        internal void ChangeSelectedItem(object p)
        {
            TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
            item.IsSelected = true;
        }
    }
}

Добавьте объявление пространства имен, содержащее этот класс, в ваш XAML и выполните привязку следующим образом (local - это то, как я назвал пространство именобъявление):

<TreeView ItemsSource="{Binding Path=Root.Children}"
          local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>

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

6 голосов
/ 28 мая 2015

Очень необычный, но довольно эффективный способ решить эту проблему, приемлемый для MVVM, заключается в следующем:

  1. Создайте свернутый видимость ContentControl в том же представлении, что и TreeView.Назовите его соответствующим образом и привяжите его содержимое к некоторому свойству SelectedSomething в viewmodel.Этот ContentControl будет «удерживать» выбранный объект и обрабатывать его привязку, OneWayToSource;
  2. Прослушать SelectedItemChanged в TreeView и добавить обработчик в коде для установки вашего ContentControl.Content для вновь выбранного элемента.

XAML:

<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
    SelectedItemChanged="TreeView_SelectedItemChanged">

Код сзади:

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemHelper.Content = e.NewValue;
    }

ViewModel:

    public object SelectedObject  // Class is not actually "object"
    {
        get { return _selected_object; }
        set
        {
            _selected_object = value;
            RaisePropertyChanged(() => SelectedObject);
            Console.WriteLine(SelectedObject);
        }
    }
    object _selected_object;
4 голосов
/ 23 августа 2011

Используйте режим привязки OneWayToSource. Это не работает.См. Edit.

Edit : Похоже, что это ошибка или «намеренное» поведение от Microsoft, согласно этот вопрос ;Тем не менее, есть некоторые обходные пути.Работает ли что-нибудь из этого для вашего TreeView?

Проблема Microsoft Connect: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings

Опубликовано Microsoft 10.01.2010 в 14:46

Сегодня мы не можем сделать это в WPF, по той же причине, по которой мы не можем поддерживать привязки к свойствам, которые не являются DependencyProperties.Состояние привязки для каждого экземпляра среды выполнения хранится в выражении BindingExpression, которое мы сохраняем в EffectiveValueTable для целевого объекта DependencyObject.Когда целевое свойство не является DP или DP доступно только для чтения, нет места для хранения BindingExpression.

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

Спасибо за ваш отзыв.

2 голосов
/ 10 февраля 2012

Я решил использовать комбинацию кода позади и кода модели представления. XAML выглядит так:

<TreeView 
                    Name="tvCountries"
                ItemsSource="{Binding Path=Countries}"
                ItemTemplate="{StaticResource ResourceKey=countryTemplate}"   
                    SelectedValuePath="Name"
                    SelectedItemChanged="tvCountries_SelectedItemChanged">

Код позади

private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
        if (vm != null)
        {
            var treeItem = sender as TreeView;
            vm.TreeItemSelected = treeItem.SelectedItem;
        }
    }

И в viewmodel есть объект TreeItemSelected, к которому вы затем можете обратиться в viewmodel.

1 голос
/ 15 января 2013

Вы всегда можете создать DependencyProperty, который использует ICommand, и прослушивать событие SelectedItemChanged в TreeView.Это может быть немного проще, чем привязка IsSelected, но я полагаю, что вы в любом случае завяжете привязку IsSelected по другим причинам.Если вы просто хотите привязать IsSelected, вы всегда можете сделать так, чтобы ваш элемент отправлял сообщение всякий раз, когда IsSelected изменяется.Затем вы можете слушать эти сообщения в любом месте вашей программы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...