HierarchicalDataTemplateアイテムからTreeViewItemを取得する方法は?
-
03-07-2019 - |
質問
HierarchicalDataTemplate
を使用してデータをバインドする TreeView
があります。
次のようになります:
<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:MyTreeViewItemViewModel}"
ItemsSource="{Binding Children}">
<!-- code code code -->
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
今、メインウィンドウなどのコードビハインドから、現在選択されている TreeViewItem
を取得します。ただし、次を使用する場合:
this.mainTreeList.SelectedItem;
selectedItemは、 MyTreeViewItemViewModel
タイプです。しかし、「親」または「バインド」 TreeViewItem
を取得したいです。それを TreeViewItemModel
オブジェクトに渡しません(方法もわかりません)。
これを行うにはどうすればよいですか
解決
これに関する Bea Stollnitz のブログエントリから、お試しください
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
他のヒント
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
機能しません(私にとって)HierarchicalDataTemplateを使用するツリービューのmainTreeList.Items.CurrentPositionは常に-1になります。
HierarchicalDataTemplateを使用するツリービューのmainTreeList.Items.CurrentItemとして次のいずれかは常にnullです。
TreeViewItem item = (TreeViewItem)mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.Items.CurrentItem);
INSTEAD ルーティングされたTreeViewItem.Selectedイベントで最後に選択されたTreeViewItemを設定して、ツリービューにバブルします(HierarchicalDataTemplateを使用しているため、設計時にTreeViewItem自体は存在しません) )。
イベントはXAMLで次のようにキャプチャできます:
<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../>
次に、最後に選択されたTreeViewItemをイベントで次のように設定できます。
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
// set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null)
this.lastSelectedTreeViewItem = tvi;
...
}
この同じ問題に遭遇しました。 TreeViewItemにアクセスして、選択できるようにする必要がありました。 その後、ViewModelにIsSelectedプロパティを追加し、それをTreeViewItems IsSelectedPropertyにバインドできることに気付きました。 これはItemContainerStyleで実現できます:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
ツリービュー内のアイテムを選択したい場合は、ViewModelクラスでIsSelectedを直接呼び出します。
誰かの助けになることを願っています。
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.
方法について
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.SelectedItem)));
これは私にとってはうまくいきます。
F&#235; anor&#8217;の回答に触発され、 TreeViewItem
が持つすべてのデータ項目に対して TreeViewItem
に簡単にアクセスできるようにしようとしましたのために作成されました。
アイデアは、 TreeViewItem
型のフィールドをビューモデルに追加し、これもインターフェイスを介して公開し、 TreeView
が TreeViewItem
コンテナが作成されます。
これは、 TreeView
をサブクラス化し、 ItemContainerGenerator
にイベントを添付することで実行されます。このイベントは、作成されるたびに TreeViewItem
を記録します。落とし穴には、 TreeViewItem
が遅延して作成されるという事実が含まれているため、特定の時間に実際に使用できるものがない場合があります。
この回答を投稿してから、これをさらに開発し、1つのプロジェクトで長い間使用しました。これまでのところ、これがMVVMに違反しているという事実以外の問題はありません(ただし、単純なケースでは大量の定型文を節約できます)。ソースこちら。
使用法
選択したアイテムの親を選択して折りたたみ、ビュー内にあることを確認します:
...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...
宣言:
<Window ...
xmlns:tvi="clr-namespace:TreeViewItems"
...>
...
<tvi:TreeViewWithItem x:Name="myTreeView">
<HierarchicalDataTemplate DataType = "{x:Type src:MyItem}"
ItemsSource = "{Binding Children}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</tvi:TreeViewWithItem>
...
</Window>
class MyItem : IHasTreeViewItem
{
public string Name { get; set; }
public ObservableCollection<MyItem> Children { get; set; }
public MyItem Parent;
public TreeViewItem TreeViewItem { get; set; }
...
}
コード
public class TreeViewWithItem : TreeView
{
public TreeViewWithItem()
{
ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
var generator = sender as ItemContainerGenerator;
if (generator.Status == GeneratorStatus.ContainersGenerated)
{
int i = 0;
while (true)
{
var container = generator.ContainerFromIndex(i);
if (container == null)
break;
var tvi = container as TreeViewItem;
if (tvi != null)
tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
var item = generator.ItemFromContainer(container) as IHasTreeViewItem;
if (item != null)
item.TreeViewItem = tvi;
i++;
}
}
}
}
interface IHasTreeViewItem
{
TreeViewItem TreeViewItem { get; set; }
}
次のようなものを試してください:
public bool UpdateSelectedTreeViewItem(PropertyNode dateItem, ItemsControl itemsControl)
{
if (itemsControl == null || itemsControl.Items == null || itemsControl.Items.Count == 0)
{
return false;
}
foreach (var item in itemsControl.Items.Cast<PropertyNode>())
{
var treeViewItem = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeViewItem == null)
{
continue;
}
if (item == dateItem)
{
treeViewItem.IsSelected = true;
return true;
}
if (treeViewItem.Items.Count > 0 && UpdateSelectedTreeViewItem(dateItem, treeViewItem))
{
return true;
}
}
return false;
}
単一の「ItemContainerGenerator.ContainerFromItem」または「ItemContainerGenerator.ItemContainerGenerator」呼び出しでは、ツリービューアイテムコントローラーとツリービューアイテムデータが分離されているため、ツリービューオブジェクトに関連付けられたTreeViewItemを見つけることができません。 ツリービューでTreeViewItemを見つけるには、 'ItemContainerGenerator.ContainerFromItem'および 'ItemContainerGenerator.ItemContainerGenerator'を使用する再帰関数を作成する必要があります。 再帰関数の例は次のようになります。
private TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object tvio)
{
var item = container.ContainerFromItem(tvio) as TreeViewItem;
if (item != null)
{
return item;
}
for (int i = 0; i < container.Items.Count; i++)
{
var subContainer = (TreeViewItem)container.ContainerFromIndex(i);
if (subContainer != null)
{
item = GetTreeViewItemFromObject(subContainer.ItemContainerGenerator, tvio);
if (item != null)
{
return item;
}
}
}
return null;
}
呼び出し元 var target = GetTreeViewItemFromObject(treeView.ItemContainerGenerator、item);
再帰関数は、ツリービューの「ItemContainerGenerator.Status」が「ContainersGenerated」になった後にのみ機能します。そのため、初期化ビュー期間中、「GetTreeViewItemFromObject」は機能しません。
ウィリアムの再帰検索をよりコンパクトなバージョンに変更しました:
public TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object targetObject) {
if (container.ContainerFromItem(targetObject) is TreeViewItem target) return target;
for (int i = 0; i < container.Items.Count; i++)
if ((container.ContainerFromIndex(i) as TreeViewItem)?.ItemContainerGenerator is ItemContainerGenerator childContainer)
if (GetTreeViewItemFromObject(childContainer, targetObject) is TreeViewItem childTarget) return childTarget;
return null;
}
TreeViewインスタンスのItemContainerGeneratorとターゲットデータオブジェクトを提供することで呼び出すことができます:
TreeViewItem tvi = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, targetDataObject);
表示されているものを変更するため、TreeViewItemが必要ですか?その場合は、TreeViewItemを直接変更する代わりにコードビハインドを使用するのではなく、スタイルを使用してアイテムの表示方法を変更することをお勧めします。うまくいけばきれいになります。
子供の子供のアイテムを見つける必要がある場合、このような再帰を使用する必要がある場合があります
public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview
{
if (item == null)
return false;
TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem;
if (child != null)
{
child.IsSelected = true;
return true;
}
foreach (object c in item.Items)
{
bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select);
if (result == true)
return true;
}
return false;
}
ここに解決策があります。 rtvEsaはツリービューです。 HierarchicalDataTemplateはツリービューテンプレートです タグは、現在のアイテムを実際に消費します。これは選択されたアイテムではなく、HierarchicalDataTemplateを使用するツリーコントロールの現在のアイテムです。
Items.CurrentItemは、内部ツリーコレクションの一部です。 多くの異なるデータを取得することはできません。たとえば、Items.ParenItemも同様です。
<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">
<TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" />