0
votes

In short:

TreeView is bound to ObservableCollection of ViewModels. Click on a node with long lasting operation and moving mouse on other node while operation lasts, results in selecting the hovered node unintendedly (WPF does that, no mouseclick by user). -> Why, and how can I stop that?

Long version:

I have a TreeView with a Hierarchical-DataTemplate bound to an ObservableCollection of custom ViewModels. Building up the node structure works fine as well as running most of the commands.

Here are the events of the DataTemplate-Items that are bound to commands in the ViewModel using an EventToCommandBehaviour:

  • PreviewMouseRightButtonDown
  • PreviewMouseRightButtonUp
  • PreviewMouseLeftButtonDown
  • PreviewMouseLeftButtonUp
  • PreviewMouseMove
  • Drop

All of the commands are fired successfully. Here´s the XAML datatemplate:

<DataTemplate DataType="{x:Type model:TreeNodeBaseViewModel}">
    <StackPanel Orientation="Horizontal" Height="16">
        <StackPanel Orientation="Horizontal" Height="16" ToolTip="{Binding ToolTip}" IsHitTestVisible="True" Background="Transparent">
            <Grid Height="16" Width="16" Margin="0,0,5,0" Visibility="{Binding ShowIcon, Converter={StaticResource BooleanToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}">
                <Viewbox Width="{Binding PackIcon.Width}" Height="{Binding PackIcon.Height}">
                    <iconPacks:PackIconSimpleIcons  Foreground="{Binding PackIcon.Color}" Rotation="{Binding PackIcon.Vector_Angle}" HorizontalAlignment="Center" VerticalAlignment="Center" Kind="{Binding PackIcon.Value, Mode=OneWay}" />
                </Viewbox>
            </Grid>
            <Label Content="{Binding DisplayName}" AllowDrop="{Binding IsDropAllowed}" Foreground="{Binding Color}" Padding="0"/>
            <i:Interaction.Behaviors>
                <beh:EventToCommandBehavior Command="{Binding PreviewMouseRightButtonDownCommand}" Event="PreviewMouseRightButtonDown" PassArguments="True" />
                <beh:EventToCommandBehavior Command="{Binding PreviewMouseRightButtonUpCommand}" Event="PreviewMouseRightButtonUp" PassArguments="True" />
                <beh:EventToCommandBehavior Command="{Binding PreviewMouseLeftButtonDownCommand}" Event="PreviewMouseLeftButtonDown" PassArguments="True" />
                <beh:EventToCommandBehavior Command="{Binding PreviewMouseLeftButtonUpCommand}" Event="PreviewMouseLeftButtonUp" PassArguments="True" />
                <beh:EventToCommandBehavior Command="{Binding PreviewMouseMoveCommand}" Event="PreviewMouseMove" PassArguments="True" />
                <beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" />
            </i:Interaction.Behaviors>
        </StackPanel>
        <Button Background="Transparent" BorderThickness="0" Margin="5,0,0,0" Visibility="{Binding IsNodePropertyButtonVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
            <iconPacks:PackIconMaterial  Width="10" Height="10" Foreground="LightGray" HorizontalAlignment="Center" VerticalAlignment="Center" Kind="InformationOutline" />
            <i:Interaction.Behaviors>
                <beh:EventToCommandBehavior Command="{Binding PropertiesCommand}" Event="Click" PassArguments="True" />
            </i:Interaction.Behaviors>
        </Button>
    </StackPanel>
</DataTemplate>

The following TreeViewItem properties are also bound to the ViewModel:

  • IsEnabled
  • AllowDrop
  • IsExpanded
  • IsSelected
  • Tag

Here´s the XAML:

<Style x:Key="MenuItemTemplateItemContainerStyle" TargetType="{x:Type TreeViewItem}">
    <Setter Property="ContextMenu" Value="{DynamicResource MenuItemContextMenu}"/>
    <Setter Property="IsEnabled" Value="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    <Setter Property="AllowDrop" Value="{Binding IsDropAllowed, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    <Setter Property="IsSelected" Value="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    <Setter Property="Tag" Value="{Binding}"/>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=IsVisible}" Value="False">
            <Setter Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
        <Trigger Property="beh:TreeNodeMouseOver.IsMouseDirectlyOverItem" Value="True">
            <Setter Property="Background" Value="AliceBlue" />
        </Trigger>
    </Style.Triggers>
</Style>

My Problem is:

When I left click on a node that triggers a longer running operation two things happen:

  1. The IsSelected in the ViewModel is not always set, although it is bound to the IsSelected of the TreeViewItem
  2. If I move the mouse on another node whilst the operation of the other node is still running the selection will change to select the hovered node without my doing anything. Why is this happening?

A few things I´ve I´ve tried:

  1. Set IsSelected manually if it has not been set – works ok, but shouldn´t have to be done.
  2. By debugging and looking at the call stack when IsSelected changes I found out that WPF seems to trigger Select and ChangeSelection when another node gets the focus – but why? …and how can I suppress this?
  3. Played around with TreeViewItem.GotFocus event to prevent the unintended selection

Here´s part of the call stack when IsSelected is set unintentionally. WPF seems to trigger Select and ChangeSelection after OnGotFocus is called:

[Native to Managed Transition]  
[Managed to Native Transition]  
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.SetValue(object item, object value)   Unknown
PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.UpdateValue(object value)   Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpression.UpdateSource(object value = false)  Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.UpdateValue()   Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpression.UpdateOverride()    Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.Update()    Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.ProcessDirty()  Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.Dirty() Unknown
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.SetValue(System.Windows.DependencyObject d, System.Windows.DependencyProperty dp, object value) Unknown
WindowsBase.dll!System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty dp = {System.Windows.DependencyProperty}, object value = false, System.Windows.PropertyMetadata metadata = {System.Windows.FrameworkPropertyMetadata}, bool coerceWithDeferredReference = false, bool coerceWithCurrentValue = false, System.Windows.OperationType operationType = Unknown, bool isInternal)   Unknown
WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyProperty dp, object value)    Unknown
PresentationFramework.dll!System.Windows.Controls.TreeView.ChangeSelection(object data = {HOSEC.UI.Tree.ViewModels.TreeNodeMainNodeViewModel}, System.Windows.Controls.TreeViewItem container = {System.Windows.Controls.TreeViewItem}, bool selected = true)   Unknown
PresentationFramework.dll!System.Windows.Controls.TreeViewItem.Select(bool selected = true) Unknown
PresentationFramework.dll!System.Windows.Controls.TreeViewItem.OnGotFocus(System.Windows.RoutedEventArgs e = {System.Windows.RoutedEventArgs})  Unknown
PresentationCore.dll!System.Windows.UIElement.IsFocused_Changed(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e) Unknown
WindowsBase.dll!System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)  Unknown
PresentationFramework.dll!System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)    Unknown
WindowsBase.dll!System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs args)    Unknown
WindowsBase.dll!System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex entryIndex, System.Windows.DependencyProperty dp = {System.Windows.DependencyProperty}, System.Windows.PropertyMetadata metadata, System.Windows.EffectiveValueEntry oldEntry, ref System.Windows.EffectiveValueEntry newEntry = {System.Windows.EffectiveValueEntry}, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType)    Unknown
WindowsBase.dll!System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty dp, object value, System.Windows.PropertyMetadata metadata, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType, bool isInternal)    Unknown
WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyPropertyKey key, object value)    Unknown
PresentationCore.dll!System.Windows.Input.FocusManager.OnFocusedElementChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)  Unknown
...
1
What's your PreviewMouseMove handler doing? As a side note, there isn't any reason to specify UpdateSourceTrigger=PropertyChanged, that's the default. And TwoWay is useless for IsEnabled & AllowDrop since those aren't set by the control.SledgeHammer
Thanks for answering. I´ve fixed the issue now, see my answer below. It had nothing to do with the ProviewMouseMove, but with losing and trying to regain focus during the long running operation when left clicking.Chris

1 Answers

0
votes

Ok, this is definitely weird WPF behaviour, but I fixed it now:

First of all I deactivated all Commands except for the PreviewMouseLeftButtonDownCommand and found out that the TreeView loses focus during the long lasting operation. When trying to regain focus for some reason WPF sets the focus to the currently hovered node - which is reproducable and requires no user interaction.

What I did was to introduce a boolean value DisableAutoSelection to the MainViewModel and then prevent the update of the IsSelected value in the TreeNodeViewModel if DisableAutoSelection is true.

Also I moved the code in PreviewMouseLeftButtonDownCommand of the TreeNodeViewModel to an asynchronous task and set DisableAutoSelection = true before running the task. When awaiting the task is completed, I set DisableAutoSelection = false.

Voila, it works now and times of randomly selected nodes are over :-)