組み込みの WPF TreeView コントロールでは、ListBox のように複数選択ができません。TreeView をカスタマイズして、書き換えずに複数選択できるようにするにはどうすればよいですか。
4 に答える
コントロールの基本的な動作 (ツリービューなど) をオーバーライドすることを検討するとき、私は常に、自分の決定に関連する使いやすさと労力を考慮するのが好きです。
ツリービューの特定のケースでは、ゼロ、1 つ、または複数のコントロールと組み合わせてリストビューに切り替えると、多くの場合実装が容易な、より使いやすいソリューションになることがわかりました。
例として、一般的な [開く] ダイアログまたは Windows エクスプローラー アプリケーションを考えてみましょう。
各treeviewitemのテキストの前にチェックボックスを追加して、このタスクを簡略化しました。
そこで、チェックボックスとテキストブロックの2つのアイテムを含むドックパネルを作成しました。
それで...
XAML
<TreeView x:Name="treeViewProcesso" Margin="1,30.351,1,5" BorderBrush="{x:Null}" MinHeight="250" VerticalContentAlignment="Top" BorderThickness="0" >
<TreeViewItem Header="Documents" x:Name="treeView" IsExpanded="True" DisplayMemberPath="DocumentsId" >
</TreeViewItem>
</TreeView>
CS
TreeViewItem treeViewItem = new TreeViewItem();
DockPanel dp = new DockPanel();
CheckBox cb = new CheckBox();
TextBlock tb = new TextBlock();
tb.Text = "Item";
dp.Children.Add(cb);
dp.Children.Add(tb);
treeViewItem.Header = dp;
treeViewItem.Selected += new RoutedEventHandler(item_Selected);
treeView.Items.Add(treeViewItem);
そして、チェックボックスの値にアクセスできます。
void item_Selected(object sender, RoutedEventArgs e)
{
selectedTVI = ((TreeViewItem)sender);
CheckBox cb = (Checkbox)((DockPanel)selectedTVI.Header).Children[0];
}
これは、複雑なものが必要ない場合に行う簡単な方法です。
最後に、内部に TreeView を含む独自の CustomControl のコーディングを終了しました。他の人の作業に基づいて、機能の鍵は、TreeView のモデルのすべての項目がインターフェイス ISelectable を継承することにあります。
public interface ISelectable
{
public bool IsSelected {get; set}
}
このようにして、TreeViewItem IsSelected とは関係のない新しい「IsSelected」プロパティが作成されます。ツリーがモデルの IsSelected プロパティを処理するようにスタイルを設定するだけです。コードは次のとおりです ( http://code.google.com/p/gong-wpf-dragdrop/で入手できるドラッグ アンド ドロップ ライブラリを使用しています):
XAML
<UserControl x:Class="Picis.Wpf.Framework.ExtendedControls.TreeViewEx.TreeViewEx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DragAndDrop="clr-namespace:Picis.Wpf.Framework.DragAndDrop">
<TreeView ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource AncestorType=UserControl}}"
ItemContainerStyle="{Binding ItemContainerStyle, RelativeSource={RelativeSource AncestorType=UserControl}}"
DragAndDrop:DragDrop.DropHandler ="{Binding DropHandler, RelativeSource={RelativeSource AncestorType=UserControl}}"
PreviewMouseDown="TreeViewOnPreviewMouseDown"
PreviewMouseUp="TreeViewOnPreviewMouseUp"
x:FieldModifier="private" x:Name="InnerTreeView" >
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White" />
</Style.Resources>
</Style>
</TreeView.Resources>
</TreeView>
C#:
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using GongSolutions.Wpf.DragDrop;
using DragDrop = GongSolutions.Wpf.DragDrop;
namespace <yournamespace>.TreeViewEx
{
public partial class TreeViewEx : UserControl
{
#region Attributes
private TreeViewItem _lastItemSelected; // Used in shift selections
private TreeViewItem _itemToCheck; // Used when clicking on a selected item to check if we want to deselect it or to drag the current selection
private bool _isDragEnabled;
private bool _isDropEnabled;
#endregion
#region Dependency Properties
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<ISelectable>), typeof(TreeViewEx));
public IEnumerable<ISelectable> ItemsSource
{
get
{
return (IEnumerable<ISelectable>)this.GetValue(TreeViewEx.ItemsSourceProperty);
}
set
{
this.SetValue(TreeViewEx.ItemsSourceProperty, value);
}
}
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(TreeViewEx));
public DataTemplate ItemTemplate
{
get
{
return (DataTemplate)GetValue(TreeViewEx.ItemTemplateProperty);
}
set
{
SetValue(TreeViewEx.ItemTemplateProperty, value);
}
}
public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(TreeViewEx));
public Style ItemContainerStyle
{
get
{
return (Style)GetValue(TreeViewEx.ItemContainerStyleProperty);
}
set
{
SetValue(TreeViewEx.ItemContainerStyleProperty, value);
}
}
public static readonly DependencyProperty DropHandlerProperty = DependencyProperty.Register("DropHandler", typeof(IDropTarget), typeof(TreeViewEx));
public IDropTarget DropHandler
{
get
{
return (IDropTarget)GetValue(TreeViewEx.DropHandlerProperty);
}
set
{
SetValue(TreeViewEx.DropHandlerProperty, value);
}
}
#endregion
#region Properties
public bool IsDragEnabled
{
get
{
return _isDragEnabled;
}
set
{
if (_isDragEnabled != value)
{
_isDragEnabled = value;
DragDrop.SetIsDragSource(this.InnerTreeView, _isDragEnabled);
}
}
}
public bool IsDropEnabled
{
get
{
return _isDropEnabled;
}
set
{
if (_isDropEnabled != value)
{
_isDropEnabled = value;
DragDrop.SetIsDropTarget(this.InnerTreeView, _isDropEnabled);
}
}
}
#endregion
#region Public Methods
public TreeViewEx()
{
InitializeComponent();
}
#endregion
#region Event Handlers
private void TreeViewOnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Shape || e.OriginalSource is Grid || e.OriginalSource is Border) // If clicking on the + of the tree
return;
TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource);
if (item != null && item.Header != null)
{
this.SelectedItemChangedHandler(item);
}
}
// Check done to avoid deselecting everything when clicking to drag
private void TreeViewOnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_itemToCheck != null)
{
TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource);
if (item != null && item.Header != null)
{
if (!TreeViewEx.IsCtrlPressed)
{
GetTreeViewItems(true).Select(t => t.Header).Cast<ISelectable>().ToList().ForEach(f => f.IsSelected = false);
((ISelectable)_itemToCheck.Header).IsSelected = true;
_lastItemSelected = _itemToCheck;
}
else
{
((ISelectable)_itemToCheck.Header).IsSelected = false;
_lastItemSelected = null;
}
}
}
}
#endregion
#region Private Methods
private void SelectedItemChangedHandler(TreeViewItem item)
{
ISelectable content = (ISelectable)item.Header;
_itemToCheck = null;
if (content.IsSelected)
{
// Check it at the mouse up event to avoid deselecting everything when clicking to drag
_itemToCheck = item;
}
else
{
if (!TreeViewEx.IsCtrlPressed)
{
GetTreeViewItems(true).Select(t => t.Header).Cast<ISelectable>().ToList().ForEach(f => f.IsSelected = false);
}
if (TreeViewEx.IsShiftPressed && _lastItemSelected != null)
{
foreach (TreeViewItem tempItem in GetTreeViewItemsBetween(_lastItemSelected, item))
{
((ISelectable)tempItem.Header).IsSelected = true;
_lastItemSelected = tempItem;
}
}
else
{
content.IsSelected = true;
_lastItemSelected = item;
}
}
}
private static bool IsCtrlPressed
{
get
{
return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
}
}
private static bool IsShiftPressed
{
get
{
return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
}
}
private TreeViewItem GetTreeViewItemClicked(UIElement sender)
{
Point point = sender.TranslatePoint(new Point(0, 0), this.InnerTreeView);
DependencyObject visualItem = this.InnerTreeView.InputHitTest(point) as DependencyObject;
while (visualItem != null && !(visualItem is TreeViewItem))
{
visualItem = VisualTreeHelper.GetParent(visualItem);
}
return visualItem as TreeViewItem;
}
private IEnumerable<TreeViewItem> GetTreeViewItemsBetween(TreeViewItem start, TreeViewItem end)
{
List<TreeViewItem> items = this.GetTreeViewItems(false);
int startIndex = items.IndexOf(start);
int endIndex = items.IndexOf(end);
// It's possible that the start element has been removed after it was selected,
// I don't find a way to happen on the end but I add the code to handle the situation just in case
if (startIndex == -1 && endIndex == -1)
{
return new List<TreeViewItem>();
}
else if (startIndex == -1)
{
return new List<TreeViewItem>() {end};
}
else if (endIndex == -1)
{
return new List<TreeViewItem>() { start };
}
else
{
return startIndex > endIndex ? items.GetRange(endIndex, startIndex - endIndex + 1) : items.GetRange(startIndex, endIndex - startIndex + 1);
}
}
private List<TreeViewItem> GetTreeViewItems(bool includeCollapsedItems)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
for (int index = 0; index < this.InnerTreeView.Items.Count; index++)
{
TreeViewItem item = (TreeViewItem)this.InnerTreeView.ItemContainerGenerator.ContainerFromIndex(index);
returnItems.Add(item);
if (includeCollapsedItems || item.IsExpanded)
{
returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems));
}
}
return returnItems;
}
private static IEnumerable<TreeViewItem> GetTreeViewItemItems(TreeViewItem treeViewItem, bool includeCollapsedItems)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
for (int index = 0; index < treeViewItem.Items.Count; index++)
{
TreeViewItem item = (TreeViewItem)treeViewItem.ItemContainerGenerator.ContainerFromIndex(index);
if (item != null)
{
returnItems.Add(item);
if (includeCollapsedItems || item.IsExpanded)
{
returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems));
}
}
}
return returnItems;
}
#endregion
}
}