WPF Highlight background when dragged over - wpf

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.

Related

WPF: DataTrigger with multi properties condition

So I have this DataTrigger:
<DataTrigger Binding="{Binding Path=IsFilesSelected}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="EndAnimation"/>
<BeginStoryboard Name="NewAnimation">
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin"
From="0,50,0,0"
To="0,0,0,0"
DecelerationRatio=".9"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
And as you can see this DataTrigger is started when my IsFilesSelected property changes to True and I wonder how to assign another property and consider its value too when determine if I need to start my DataTrigger.
What you are looking for is the MultiDataTrigger with documentation here.
Represents a trigger that applies property values or performs actions when the bound data meet a set of conditions.
Example lifted from the MSDN website
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Name}" Value="Portland" />
<Condition Binding="{Binding Path=State}" Value="OR" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Cyan" />
</MultiDataTrigger>
The above example will set the Background property when both the Name == "Portland" AND the State == "OR".

Changing Property in ThicknessAnimation

I'm using a label in my project to show some news in moving style by thicknessanimation, but I can't change the content of label after the animation completes. What can I do?
First when the window loads I activate a thicknessanimation with the code below
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard TargetName="MyLabel">
<ThicknessAnimation Storyboard.TargetProperty="Margin" SpeedRatio="0.8" RepeatBehavior="Forever"
FillBehavior="HoldEnd" From="0,0,0,0" To="100,0,0,0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
Then I Set a Style on the label
<Style TargetType="Label">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=MyLabel, Path=Margin}" Value="100,0,0,0" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapes" />
</MultiDataTrigger>
</Style.Triggers>
I tried to change the content with IsVisibleChanged but it didn't work.
Based on your code snippet the first issue is your MultiDataTrigger. You have the condition and the setter backwards try something like this and see if it works
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="Visibility" Value="Collapsed" />
</MultiDataTrigger.Conditions>
<Setter Binding="{Binding ElementName=Label1, Path=Margin}" Value="0,0,0,0"/>
The Condition tag is for the event or property you're looking for to trigger the Setter tag which will set the properties of the element you want to change.
Also a MultiDataTrigger is probably not the tag you need to change the margin this code should be a bit simpler
<Style TargetType="Label">
<Style.Triggers>
<Trigger Property="Visibility" Value="Collapsed">
<Setter Property="Margin" TargetName="MyLabel" Value="0,0,0,0"/>
</Trigger>
</Style.Triggers>

Animate background colour on exitaction

I'm trying to animate the background colour on a grid to change, once an event happens, but I can't get it working, I can get it to change colour immediately (via data triggers), but as soon as I try to introduce an animation into it, then I can't get it working (the animation doesn't seem to come into effect).
This is the current XAML I'm using (though I've tried various variations and cannot get it to animate):
<DataTrigger Binding="{Binding ElementName=me, Path=Viewed}" Value="False">
<Setter Property="Background" Value="LightYellow" />
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:02" To="White" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<!--
<DataTrigger Binding="{Binding ElementName=me, Path=Viewed}" Value="True">
<Setter Property="Background" Value="White" />
</DataTrigger>
-->
Where Viewed is a dependency property (bool) on my Control. Any hints in the right direction would be appreciated. I've also tried setting it as an EventTrigger on a raised event which happens when the bool switches to true.
Thanks to Clemens helps, figured out what I needed to do:
<SolidColorBrush x:Key="GridColourBrush" Color="LightYellow" />
<Style x:Key="GridStyle" TargetType="Grid">
<Setter Property="Background" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=me, Path=Viewed}" Value="False">
<Setter Property="Background" Value="{StaticResource GridColourBrush}" />
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:02" To="White" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- snipped stuff -->
<Grid MinWidth="525" x:Name="ContainerGrid" Style="{StaticResource GridStyle}" Background="{StaticResource GridColourBrush}" />
So setting the background to be flat white by default, then if the DP bool is false, change the background to the static solid colour brush, which I can then animate via the exit actions.
What i meant was simply that instead of
<Grid Background="LightYellow">
</Grid>
you would have to write
<Grid>
<Grid.Background>
<SolidColorBrush Color="LightYellow" />
</Grid.Background>
</Grid>
No need to have an extra resource.

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.

How to prevent selection of a TreeViewItem based on a condition

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>

Resources