How to prevent selection of a TreeViewItem based on a condition - wpf

I have wpf TreeView -- bound to some data.
The Treeview resides on the left hand of a window divided into two areas where the tree is the navigation and a panel on the right side changes content depending on the tree node selected.
Not all the nodes of the treeview produce detail information.
I want to disable the selection of those nodes. Any idea?
Thanks

#jama64 : You can achieve what you want if you change the Style from Property IsEnabled to Focusable.
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Focusable" Value="{Binding HasDetails}"/>
</Style>
</TreeView.ItemContainerStyle>

Do you have something like a boolean property in your source called HasDetails or something? In that case you can use something like this. Create a MultiDataTrigger in the ItemContainerStyle that binds to HasDetails in the DataContext and IsSelected for the TreeViewItem and if both are True (well, True that HasDetails is False:-), you start a Storyboard that "unselects" the newly selected TreeViewItem.
This will disable selection for all the TreeViewItem's that doesn't have details but they will still be expandable. Hopefully that was what you were looking for
<TreeView ...>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasDetails}" Value="False"/>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetProperty="(TreeViewItem.IsSelected)">
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Update
To disable the TreeViewItem's where HasDetails is False you can use this
<TreeView ...>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsEnabled" Value="{Binding HasDetails}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>

Related

WPF Highlight background when dragged over

I have successfully implemented drag and drop in my application. An am now working on improving the user experience.
My aim is when a drag starts to highlight my possible targets, and then if they are dragged over change to a different colour.
I came up with this which almost works, however it seems to sometimes miss the DragLeave Event. The style is applied to any of my controls that are being used as a drop target (multiple types)
<Style x:Key="HighlightDrop">
<Setter Property="Control.Background" Value="Orange" /> <!-- usually set to transparent, just set to orange here to make it obvious-->
<Style.Triggers>
<EventTrigger RoutedEvent="UIElement.DragEnter">
<BeginStoryboard x:Name="Highlight">
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="Background.Color"
Duration="0:0:0">
<DiscreteColorKeyFrame Value="LightGreen" KeyTime="0:0:0" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.DragLeave">
<StopStoryboard BeginStoryboardName="Highlight" />
</EventTrigger>
<DataTrigger Binding="{Binding AmDragging, RelativeSource={RelativeSource AncestorType=Window}}" Value="True" >
<Setter Property="Control.Background" Value="LightBlue" />
</DataTrigger>
</Style.Triggers>
</Style>
I then tried a different approach, but this didn't work wither as it appears that the IsMouseOver event doesn't work during dragging.
<Style x:Key="HighlightDrop">
<Setter Property="Control.Background" Value="Orange" />
<!-- usually set to transparent, just set to orange here to make it obvious-->
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding AmDragging, RelativeSource={RelativeSource AncestorType=Window}}" Value="True" />
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=Control}}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Control.Background" Value="LightGreen" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=Control}}" Value="True" > <!-- Just used to test that the binding for IsMouseOver is working -->
<Setter Property="Control.Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding AmDragging, RelativeSource={RelativeSource AncestorType=Window}}" Value="True" >
<Setter Property="Control.Background" Value="LightBlue" />
</DataTrigger>
</Style.Triggers>
</Style>
What am I missing here, it feels like I am re-inventing the wheel when the job should be pretty basic to acheive.
drag and drop works is quite hard to understand and annoying thing.
drag event sometimes is consumed by another control.
especially, if your xaml page has content control. content control has PreviewDragEnter, PreviewDragLeave, PreviewDragOver. you should call following source code when its event fired. e.Handled = true.
In my case, I was about to move ListBox Item ordering with drag and drop interaction. ListBox Item contains TextBox, ComboBox wich is IsEditable=true, and ContentControl. those controls were consuming drag leave event when mouse is over it while drag leaving. so I called e.Handled = true. on PreviewDragEnter, PreviewDragLeave, PreviewDragOver event of those controls.

WPF Bind or trigger element to a separate datagrid update

In wpf I have a TabControl that contains a DataGrid which is bound to an observable collection.
I want an animation to trigger when the following conditions are met:
1) The datagrid's collection updates
2) The tab is not selected.
Essentially I want the tab to flash if it's containing datagrid gets updated while the user has another tab selected.
<TabItem x:Name="tab1" Header="Tab 1">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Style.Resources>
<Storyboard>
<!-- Animation -->
</Storyboard>
</Style.Resources>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=tab1, Path=IsSelected}" Value="False"/>
<!-- Should I have a condition for datagrid1 updating? -->
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard .../>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>

BasedOn DataTriggers not Activating

I created a base style for my navigation menu. I want to change certain elements such as label content and icons based on a particular style while keeping the hover/click effects standard for all navigation items.
Here's my base XAML:
<Style x:Key="BaseNavigationStyle">
<Setter Property="Control.Cursor" Value="Hand"/>
<Setter Property="Control.Padding" Value="0,6,0,6"/>
<Setter Property="Control.Background" Value="{StaticResource NavigationItemBackgroundColorBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource NavigationItemBackgroundNormal}"/>
</DataTrigger.ExitActions>
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource NavigationItemBackgroundHover}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Here's my XAML for a Navigation Item:
<Style x:Key="ValidationNavigationStyle" BasedOn="{StaticResource BaseNavigationStyle}">
<Setter Property="Navigation:NavigationUserControl.Image">
<Setter.Value>
<BitmapImage UriSource="Assets/Navigation/validation.png"/>
</Setter.Value>
</Setter>
<Setter Property="Navigation:NavigationUserControl.Label" Value="Validation"/>
</Style>
Any ideas on why my all of the Setters (Cursor, Padding, Background) ARE being set but the MouseOver trigger is not working?
Side Note: If I cut/paste the trigger into the ValidationNavigationStyle, it will work as expected. I have a hunch that it has something to do with the RelativeSource binding but I can't quite figure out what it is.

Treeview ContextMenu binding Cut and Paste to logical tree

This seems like a pretty common scenario but I can't figure out how to bind the menu items to disable when there is nothing in the clipboard.
I've decided against using the Windows clipboard and instead store the actual object in a reference variable on the UserControl called NodeClipboard. Since it is strongly typed and implements INotifyProperty it is a lot more useful to me than the Windows clipboard.
Binding to the individual item works fine though it is extremely verbose because you can't set EventHandlers within resources without using the Style Event Setters.
It sort of looks like this...
<UserControl x:Name="PART_Root">
<TreeView x:Name="PART_Tree" ItemsSource="{Binding ElementName=PART_Root, Path=RootItemContainer}">
<TreeView.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Style>
<Style TargetType="ContextMenu">
<!-- I use this event to select the tree view item otherwise it is actually pretty difficult to know what item you right clicked on -->
<EventSetter Event="Opened" Handler="ContextMenu_Opened"/>
</Style>
</ContextMenu.Style>
<MenuItem Header="Cut">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="CutNode_Click"/>
<Style.Triggers>
<!-- This binding is fine because it binds to the item that was right clicked on -->
<DataTrigger Binding="{Binding Path=IsRoot}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Paste">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="PasteNode_Click"/>
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Id}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</UserControl>
The key part that doesn't work is this here:
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
I've tried using relative source which results in the same problem. The only solution I've thought of so far is making two context menus, one with Paste enabled and one without, and switching the context menu on the style on the TreeViewItem style's ContextMenu setter like so...
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteEnabled}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteDisabled}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
Use the Clipboard class. Then you can use the ContainsText method to determine if there is any data on the clipboard.
http://msdn.microsoft.com/en-us/library/system.windows.forms.clipboard.aspx

Change ControlTemplate of ContentControl in View using MVVM

I have two resources Dock and Undock in my View which is a UserControl(Dock.xaml), Following is xaml code
<Grid>
<ContentControl Template="{StaticResource Dock}"/>
</Grid>
In DockViewModel there is property called IsDocked,if its true i need to apply Dock otherwise Undock template
How to change the template in view using ViewModel.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentControl.Template" Value="{StaticResource Dock}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDocked}" Value="False">
<Setter Property="ContentControl.Template" Value="{StaticResource UnDock}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Resources