I managed to write it eventually.
Made a custom control for it. it still need to be refined, but it works good:
Generic.xaml :
<ControlTemplate x:Key="CommonValidationToolTipTemplate" TargetType="ToolTip">
<Grid x:Name="Root" Margin="5,0" Opacity="0" RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TranslateTransform x:Name="Translation" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="Translation">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="Translation"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="#052A2E31" CornerRadius="5" Margin="4,4,-4,-4"/>
<Border Background="#152A2E31" CornerRadius="4" Margin="3,3,-3,-3"/>
<Border Background="#252A2E31" CornerRadius="3" Margin="2,2,-2,-2"/>
<Border Background="#352A2E31" CornerRadius="2" Margin="1,1,-1,-1"/>
<Border Background="#FFDC000C" CornerRadius="2">
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" UseLayoutRounding="false"/>
</Border>
</Grid>
</ControlTemplate>
<Style TargetType="local:AutoCompleteTreeView"
xmlns:System="clr-namespace:System;assembly=mscorlib">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="#FFFFFFFF"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="MinWidth" Value="45"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoCompleteTreeView">
<Grid Opacity="{TemplateBinding Opacity}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PopupStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1" To="PopupOpened"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="PopupClosed"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="PopupOpened">
<Storyboard>
<DoubleAnimation To="1.0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PopupBorder"/>
</Storyboard>
</VisualState>
<VisualState x:Name="PopupClosed">
<Storyboard>
<DoubleAnimation To="0.0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PopupBorder"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>True</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="Text" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" IsTabStop="True" Margin="0" Padding="{TemplateBinding Padding}" Style="{TemplateBinding TextBoxStyle}"/>
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource CommonValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>true</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
<Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
</Grid>
</Border>
<Popup x:Name="Popup">
<Grid Opacity="{TemplateBinding Opacity}">
<Border x:Name="PopupBorder" BorderThickness="0" Background="#11000000" HorizontalAlignment="Stretch" Opacity="0">
<Border.RenderTransform>
<TranslateTransform X="1" Y="1"/>
</Border.RenderTransform>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="0" HorizontalAlignment="Stretch" Opacity="1.0" Padding="0">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFDDDDDD" Offset="0"/>
<GradientStop Color="#AADDDDDD" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="-1" Y="-1"/>
</TransformGroup>
</Border.RenderTransform>
<local:TreeViewSelectionAdapter x:Name="SelectionAdapter" BorderThickness="0" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ItemTemplate="{TemplateBinding ItemTemplate}" ItemContainerStyle="{TemplateBinding ItemContainerStyle}" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</Border>
</Border>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
AutoCompleteTreeView.cs:
public class AutoCompleteTreeView : AutoCompleteBox
{
public AutoCompleteTreeView()
{
DefaultStyleKey = typeof(AutoCompleteTreeView);
}
}
TreeViewSelectionAdapter.cs:
public class TreeViewSelectionAdapter : TreeView, ISelectionAdapter
{
private object lastSelectedItem = null;
/// <summary>
///This prevents the text box of the AutoCompleteBox control from being updated continuously.
/// </summary>
private bool IgnoreAnySelection { get; set; }
/// <summary>
/// An event that indicates that a selection is complete and has been
/// made, effectively a commit action.
/// </summary>
public event RoutedEventHandler Commit;
/// <summary>
/// An event that indicates that the selection operation has been
/// canceled.
/// </summary>
public event RoutedEventHandler Cancel;
/// <summary>
/// Initializes a new instance of the SelectorSelectionAdapter class.
/// </summary>
public TreeViewSelectionAdapter()
{
base.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(OnSelectionChanged);
MouseLeftButtonUp += OnSelectorMouseLeftButtonUp;
}
void OnSelectionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (IgnoreAnySelection)
{
return;
}
SelectionChangedEventHandler handler = this.SelectionChanged;
if (handler != null)
{
IList oldSelectedItem = new List<object>();
if (lastSelectedItem != null)
oldSelectedItem.Add(lastSelectedItem);
if (SelectionChanged != null)
handler(this, new SelectionChangedEventArgs(oldSelectedItem, new List<object> { this.SelectedItem }));
lastSelectedItem = this.SelectedItem;
}
}
public new object SelectedItem
{
get
{
return base.SelectedItem;
}
set
{
this.SelectItem(value);
}
}
/// <summary>
/// Handles the mouse left button up event on the selector control.
/// </summary>
private void OnSelectorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IgnoreAnySelection = false;
OnSelectionChanged(this, null);
OnCommit(this, new RoutedEventArgs());
}
public event SelectionChangedEventHandler SelectionChanged;
/// <summary>
/// Gets or sets the items source.
/// </summary>
public new IEnumerable ItemsSource
{
get { return base.ItemsSource; }
set
{
if (base.ItemsSource != null)
{
INotifyCollectionChanged notify = base.ItemsSource as INotifyCollectionChanged;
if (notify != null)
{
notify.CollectionChanged -= OnCollectionChanged;
}
}
base.ItemsSource = value;
if (base.ItemsSource != null)
{
INotifyCollectionChanged notify = base.ItemsSource as INotifyCollectionChanged;
if (notify != null)
{
notify.CollectionChanged += OnCollectionChanged;
}
}
}
}
/// <summary>
/// Handles the CollectionChanged event, resetting the selection
/// ignore flag.
/// </summary>
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
IgnoreAnySelection = true;
}
/// <summary>
/// Process a key down event.
/// </summary>
public void HandleKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
IgnoreAnySelection = false;
OnCommit(this, e);
e.Handled = true;
break;
case Key.Down:
if ((ModifierKeys.Alt & Keyboard.Modifiers) == ModifierKeys.None)
{
IgnoreAnySelection = true;
//SelectedIndexIncrement();
TreeView tv = this.FindName("SelectionAdapter") as TreeView;
tv.KeyDown += new KeyEventHandler(tv_KeyDown);
tv.Focus();
List<TreeViewItem> ls = new List<TreeViewItem>(tv.GetContainers());
ls[0].Focus();
e.Handled = true;
}
break;
case Key.Escape:
OnCancel(this, e);
e.Handled = true;
break;
default:
break;
}
}
void tv_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
IgnoreAnySelection = false;
OnSelectionChanged(this, null);
OnCommit(this, new RoutedEventArgs());
break;
}
}
/// <summary>
/// Fires the Commit event.
/// </summary>
private void OnCommit(object sender, RoutedEventArgs e)
{
RoutedEventHandler handler = Commit;
if (handler != null)
{
handler(sender, e);
}
AfterAdapterAction();
}
/// <summary>
/// Fires the Cancel event.
/// </summary>
private void OnCancel(object sender, RoutedEventArgs e)
{
RoutedEventHandler handler = Cancel;
if (handler != null)
{
handler(sender, e);
}
AfterAdapterAction();
}
/// <summary>
/// Change the selection after the actions are complete.
/// </summary>
private void AfterAdapterAction()
{
this.SetSelectedContainer(null);
}
/// <summary>
/// Initializes a new instance of a DataGridAutomationPeer.
/// </summary>
public AutomationPeer CreateAutomationPeer()
{
return new TreeViewAutomationPeer(this);
}
}
Using the control:
<ccontrols:AutoCompleteTreeView x:Name="textbox" MinimumPrefixLength="1" IsEnabled="True" ItemsSource="{Binding MyNodes}" >
<ccontrols:AutoCompleteTreeView.ItemTemplate>
<hirarch:HierarchicalDataTemplate ItemsSource="{Binding MyNodes}" >
<TextBlock Text="{Binding Name}" />
</hirarch:HierarchicalDataTemplate>
</ccontrols:AutoCompleteTreeView.ItemTemplate>
</ccontrols:AutoCompleteTreeView>
The hierarchy objects:
public class Node
{
public string Name { get; set; }
public List<Node> MyNodes { get; set; }
public override string ToString()
{
return Name;
}
}