In an wpf application I have a ListView with Drag and Drop functionality, which inserts the dropped element at the position, where it was dropped instead of the end of the list. Thats working fine. What i now want to achieve seemed to be the simple part :) I want to make clear for the user at which position in the list the currently dragged element will be dropped. There should be a gap where the new element will be inserted.
My approach was to change the Padding of the ListViewItem at the current mousePosition.
<ControlTemplate TargetType="ListViewItem">
<Border x:Name="BorderA">
<ContentPresenter />
<Border.Triggers>
<!--padding for gap when mouse hovers element-->
<EventTrigger RoutedEvent="DragEnter" SourceName="BorderA">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="Padding"
From="0,0,0,0" To="0,25,0,0"
BeginTime="0:0:0" Duration="0:0:0.5"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--back to normal padding-->
<EventTrigger RoutedEvent="DragLeave" SourceName="BorderA">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="Padding"
From="0,25,0,0" To="0,0,0,0"
BeginTime="0:0:0" Duration="0:0:0.5"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</ControlTemplate>
I also need a DataTemplate to display my listItems correctly. I simplified that here, which might make it appear nearly redundand, but in reality i need to display a complex viewModel and it seems to be part of the problem.
<DataTemplate x:Key="SortableListBoxItemTemplate" DataType="item:SortableListItemViewModel">
<Border Background="{StaticResource CallToActionBrush}"
BorderThickness="{StaticResource BorderThickness}"
BorderBrush="{StaticResource PassiveBorderBrush_Strong}">
</Border>
</DataTemplate>
That seemd to work. When i hover an element while dragging another, a gap between that element and the previous list element appears. But sometimes when the gap grows the mouse position will be within this gap. That triggeres the DragLeave Event, the gap starts closing until the listItem has reached the mouse position again and so on. That causes an ugly flickering and i am not sure how to avoid that. Everything is fine as long as the DataTemplate hasn't a Background color. But thats not the visual effect i want. Is there a way to trigger that DragEnter and DragLeave events only if they were fired from my listviewItem (or BorderA)? Or is there a complete other way to achieve the effect i want?
Is your problem the unexpected DragLeave event causing the flickering?
In this case, I guess setting a transparent background to your BorderA element would solve the problem.
<ControlTemplate TargetType="ListViewItem">
<Border x:Name="BorderA" Background="Transparent">
<ContentPresenter />
<Border.Triggers>
The transparent background will enable the hit test for your whole ListViewItem including the padding you are creating as placeholder.
I found a solution.
The problem wasn't the DragLeave event, but the DragEnter event when the cursor enters the Item (coming from the gap). This triggers the animation to start again and someone stupid has told this animation to start From="0,0,0,0"... I removed that part and everything worked just fine.
For me there is still a question. Maybe in some cases removing the From-part is not an option. Is there a way to add a condition to the event trigger. Something like a Multitrigger?
Related
I am trying to make a DataGridRow's background colour animate itself when its data changes. I have used the advice in this post and hooked up an animation to the TargetUpdated event. This animation fires when the data is first loaded, when it scrolls and when the data changes.
My biggest issue is that when I try scrolling by dragging the scrollbar with the mouse I get a crash of the following sort:
System.InvalidOperationException: Cannot animate 'Background.Color' on an immutable object
When I scroll using the arrow buttons there's no crash. If I reach the bottom of the list and only then start dragging the scrollbar with the mouse, there's no crash.
Can anyone advise as to why this crash is happening? My best guess based on other reading is that there are several animations running for a single element concurrently because the animation is triggered by scrolling (maybe due to element resuse and contentdata switching?). There's certainly nothing in the code explicitly binding to the background color.
A related issue I have is that I only want the animation to trigger when the data actually changes - not when I scroll.
I apologise that I cannot post a copy of my code. My company's policies make that problematic. Here's a paraphrase (hopefully catching all salient details):
<Style x:Key="RowStyle" TargetType="DataGridRow">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation From="Red" Duration="0:0:1" Storyboard.TargetProperty="Background.Color">
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
The columns with data I am interested in are similar to the following:
<DataGridTextColumn Binding="{Binding Value, Mode=OneWay, NotifyOnTargetUpdated=True}" />
In short, I know how to animate from a current value to a new value, but what I don't know how to do (and can't find) is how to animate back to the value before the animation ran.
In other words, say my current background is yellow. I want to flash the background as red based on some logic, but I want it to fade from that red back to yellow again. Transparent doesn't work here because it replaces the background value with 'Transparent', not fades through it.
Also, I'm not referring to the FillBehavior property which you can use to 'un-apply' the animated value after it's ran, nor am I referring to auto-reversing the animation as that would mean it would have to run forward first meaning we'd get a fade to the red, not an instant pulse as we want.
Only way I've found so far is to do this in code-behind, but that introduces its own issues with logically arranging things. I just want the 'To' value to be set to the pre-animated value. How can that be done?
Use ColorAnimation.From Property only as in this sample:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color"
From="Red" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBox Text="AAA" Background="Yellow" />
<TextBox Text="BBB" Background="Blue" />
</StackPanel>
I want to fade in/out a ToolTip how can I achieve that?
Also, I wonder if its a good idea to fix the position of the ToolTip? eg. always at bottom? I guess the ToolTip may go off screen? And suppose I want to do that, how can I do that? Position the ToolTip a fixed position relative to the parent
The position is relative to the parent unless you specify otherwise. If you want it to display somewhere else you can use
<TextBox ToolTipService.PlacementTarget="{Binding ElementName=displayToolTipHere}">
To specify the position you use Placement. Either ToolTipService.Placement in TextBox or Placement inside the ToolTip like in my example below. I don't think ToolTip has any built in "fade in/fade out" but you can use an animation. Don't think you can make it fade out though since it instantly closes once the mouse leaves the Control. If you want a fade out effect you should probably use a Popup instead.
<TextBox>
<TextBox.ToolTip>
<ToolTip Placement="Bottom"
Content="Some ToolTip Content">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="ToolTip.Opened">
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="0.0" To="1.0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</TextBox.ToolTip>
</TextBox>
If I attach a callback to the loaded event of my main user control and check the ActualWidth of a grid column, that value is still 0. I find it strange, given the fact that the documentation states: "The Loaded event is raised before the final rendering, but after the layout system has calculated all necessary values for rendering."
Basically I need the ActualWidth so I can set the 'to' value of an animation (so something animates from a width of 0 to the actual width).
The ActualWidth and ActualHeight values are available during the SizeChanged event. Which works out nice, so you can update your calculations when your applications resizes if needed.
I posted about this recently using the event to randomly postion elements on the screen
I think you're probably trying to solve the problem in the wrong way.
Your best bet is to animate a ScaleTransform that's added to the Grids' RenderTransform property. Here's an example that does just that:
<Grid x:Name="LayoutRoot">
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyScaleTransform" Storyboard.TargetProperty="ScaleX" From="0" To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Grid.Triggers>
<Grid Background="Blue" >
<Grid.RenderTransform>
<ScaleTransform x:Name="MyScaleTransform" />
</Grid.RenderTransform>
</Grid>
</Grid>
Can you hook or override the LayoutUpdated event in your control, rather than the Loaded event? You'd have to add some additional logic to make sure that your animation only ran once, at the appropriate time, but it might give you the additional flexibility you need.
I'm starting to kick Silverlight around (although it currently feels the other way around) by rewritting an existing ASP.NET application - as good a place to start as any I thought.
I have 'mastered' pulling data from a database, through a service and into a datagrid and also populating image elements in the rows. So far so good.
Now i'm stuck and have a headache!
I want to add a simple animation (a green rectangle moving over a red rectangle to display a % progress) to the last column of each row, but can't get it wired up.
My xaml looks like this:
<data:DataGrid.Columns>
.
.
.
.
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel x:Name="AnimationPanel">
<StackPanel.Resources>
<Storyboard x:Key="ShowProgress">
<DoubleAnimation Storyboard.TargetProperty="Width"
Storyboard.TargetName="goalProgressBar_Complete"
From="0" To="50"
Duration="0:0:5">
</DoubleAnimation>
</Storyboard>
</StackPanel.Resources>
<Rectangle x:Name="goalProgressBar_Complete"
Width="1" Height="100"
Fill="#0aa60e"></Rectangle>
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
which is quite obviously wrong (because it doesn't work) and I am starting to think that it just is not possible to add animation to a Datagrid in this way. I've Googled hard looking for a piece of code and while there have been some tantilising results they have not amounted to anything.
Am I trying to do something that Silverlight simply does not support or am I missing something really straightforward?
Thanks in advance.
Update:
Although I'm not out of the woods yet I have managed to get the animation running with the appropriate column.
The answer provided by MasterMax led me to look at EventTriggers and the revised xaml looks like this:
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Rectangle x:Name="GoalProgress" Height="100" Width="1" Fill="Green">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
Storyboard.TargetName="GoalProgress"
From="0" To="50" Duration="0:0:5">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
All I need to do now is bind in the % value in the 'To' property and add my underlying Red rectangle. No doubt this will not prove as easy as I'm currently hoping but then it wouldn't be fun otherwise would it ;-)
Try attaching it to the EventTrigger for the Datagrid. Something similar to this:
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Name="EntranceAnimation"
Storyboard.TargetName="MainWindow"
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:3">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
In your case you'd be looking for the DataGrid.Loaded event (or something similar). This works for me, and I'm fading the whole window in on page loaded.
It looks to me like you aren't actually playing the storyboard. You need to add mouse enter and mouse leave handlers for your rectangle and then play and stop your storyboard accordingly. Make sure to both stop and then play the storyboard in the enter command (otherwise you get some interesting side-effects).
Update
Okay, having spent my day neck deep in Silverlight, I'm not much closer to helping. I was looking at the generic.xaml file inside the Silverlight assembly containing the DataGrid. You might consider taking a look in there. From what I can see, they achieve some of this for the row details transition with control templating though I haven't looked at the corresponding code yet to determine exactly how.
for that type of application, an animation may not be the best solution. you may want to try binding the width property of the moving rectangle to a variable in your application, or adjust the width with each tick as the progress changes.