3レベルのツリービューがあります。コードから第3レベルのアイテムを選択するにはどうすればよいですか?多くのブログやスタックオーバーフローで言及されている方法を試しましたが、それは第1レベルでのみ機能するようです(第1レベルより下のアイテムではdbObjectはnullです)。
TreeViewItemを選択するために使用しているコードは次のとおりです。私は何かを逃していますか?
public static void SetSelectedItem(this TreeView control, object item)
{
try
{
var dObject = control.ItemContainerGenerator.ContainerFromItem(item);
//uncomment the following line if UI updates are unnecessary
((TreeViewItem)dObject).IsSelected = true;
MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select",
BindingFlags.NonPublic | BindingFlags.Instance);
selectMethod.Invoke(dObject, new object[] { true });
}
catch { }
}
別のオプションは、バインディングを使用することです。バインディングを使用して各TreeViewItem
のテキストを取得するオブジェクトがある場合(たとえば)、IsSelected
プロパティもバインドするスタイルを作成できます。
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.Resources>
</TreeView>
これは、バインドされたオブジェクトがIsSelected
タイプのbool
プロパティを持っていることを前提としています。次に、対応するオブジェクトのTreeViewItem
をIsSelected
に設定することにより、true
を選択できます。
同じアプローチをIsExpanded
プロパティで使用して、TreeViewItem
が展開または縮小されるタイミングを制御できます。
次のTreeView
拡張機能を使用できます。これはより簡単な解決策だと思います。
public static class TreeViewExtension
{
public static bool SetSelectedItem(this TreeView treeView, object item)
{
return SetSelected(treeView, item);
}
private static bool SetSelected(ItemsControl parent, object child)
{
if (parent == null || child == null)
return false;
TreeViewItem childNode = parent.ItemContainerGenerator
.ContainerFromItem(child) as TreeViewItem;
if (childNode != null)
{
childNode.Focus();
return childNode.IsSelected = true;
}
if (parent.Items.Count > 0)
{
foreach (object childItem in parent.Items)
{
ItemsControl childControl = parent
.ItemContainerGenerator
.ContainerFromItem(childItem)
as ItemsControl;
if (SetSelected(childControl, child))
return true;
}
}
return false;
}
}
詳しくは、こちらのブログ記事をご覧ください。 http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/
別の解決策を試した後、私は this サイトに行きました。 Zhou Yongは、TreeViewのすべてのノードをプログラムで展開する方法を示しています。彼の方法には2つの主要なアイデアがあります。
これが私が終わったコードです
public static void SelectItem(this ItemsControl parentContainer, List<object> path)
{
var head = path.First();
var tail = path.GetRange(1, path.Count - 1);
var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem;
if (itemContainer != null && itemContainer.Items.Count == 0)
{
itemContainer.IsSelected = true;
var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
selectMethod.Invoke(itemContainer, new object[] { true });
}
else if (itemContainer != null)
{
itemContainer.IsExpanded = true;
if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
itemContainer.ItemContainerGenerator.StatusChanged += delegate
{
SelectItem(itemContainer, tail);
};
}
else
{
SelectItem(itemContainer, tail);
}
}
}
私の場合(同じ問題がありました)が、DataオブジェクトのIsSelectedプロパティへのバインドを使用するのは不適切であり、ツリーアイテムへのパスを簡単に取得できなかったため、次のコードで完全に機能しました。
private void SelectTreeViewItem(object item)
{
try
{
var tvi = GetContainerFromItem(this.MainRegion, item);
tvi.Focus();
tvi.IsSelected = true;
var selectMethod =
typeof(TreeViewItem).GetMethod("Select",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
selectMethod.Invoke(tvi, new object[] { true });
}
catch { }
}
private TreeViewItem GetContainerFromItem(ItemsControl parent, object item)
{
var found = parent.ItemContainerGenerator.ContainerFromItem(item);
if (found == null)
{
for (int i = 0; i < parent.Items.Count; i++)
{
var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
TreeViewItem childFound = null;
if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
childContainer.ItemContainerGenerator.StatusChanged += (o, e) =>
{
childFound = GetContainerFromItem(childContainer, item);
};
}
else
{
childFound = GetContainerFromItem(childContainer, item);
}
if (childFound != null)
return childFound;
}
}
return found as TreeViewItem;
}
私の答えでパーティーに非常に遅れますが、純粋なMVVMソリューションが必要な場合は、イベントトリガー(ユーザーが新しいアイテムを選択したときにバインディングを更新する)とデータトリガー(値が選択されたアイテムを更新するバインディング変更の)。
これを機能させるには、メインのViewModelにアイテム、現在選択されているアイテムのプロパティ、および現在選択されているアイテムが変更されたときに呼び出されるコマンドプロパティが必要です。
public class MainViewModel : ViewModelBase
{
// the currently selected node, can be changed programmatically
private Node _CurrentNode;
public Node CurrentNode
{
get { return this._CurrentNode; }
set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); }
}
// called when the user selects a new node in the tree view
public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } }
private void OnSelectedNodeChanged(Node node)
{
this.CurrentNode = node;
}
// list of items to display in the tree view
private ObservableCollection<Node> _Items;
public ObservableCollection<Node> Items
{
get { return this._Items; }
set { this._Items = value; RaisePropertyChanged(() => this.Items); }
}
}
TreeViewは、選択が変更されたときにSelectedNodeChangedCommandを呼び出すイベントトリガーと、コードでCurrentNodeの値がプログラムによって変更されたときにコントロール項目が選択されるように、TreeViewItemスタイルのDataTriggerを必要とします。
<TreeView x:Name="treeView" ItemsSource="{Binding Items}"
xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<TreeView.Resources>
<conv:EqualityConverter x:Key="EqualityConverter" />
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="False" />
<Style.Triggers>
<!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" />
<Binding />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- *** HierarchicalDataTemplates go here *** -->
</TreeView.Resources>
<!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
DataTriggerは、CurrentNodeの値が現在のリストアイテムのNodeと一致することを検出することで機能します。残念ながら、DataTriggersはそれらの値をバインドできないため、代わりにEqualityConverterでテストする必要があります。簡単な比較:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ええ、直接の親TreeViewItemから呼び出した場合でも、ContainerFromItemメソッドは何も返しません。
少し再設計する必要があるかもしれません。すべてを明示的なTreeViewItemとして作成する場合、それへの参照を保持し、それにIsSelectedを設定できるはずです。