I was looking for a way to animate the scrolling of a ScrollViewer and I found a sample, but when I try to add the class to the XAML file I get an error:
Error 2
The type 'AniScrollViewer'
was not found. Verify that you are not
missing an assembly reference and that
all referenced assemblies have been
built.
this is the code I found in a forum and I added the class to my cs file:
public class AniScrollViewer:ScrollViewer
{
public static DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register("CurrentVerticalOffset", typeof(double), typeof(AniScrollViewer), new PropertyMetadata(new PropertyChangedCallback(OnVerticalChanged)));
public static DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register("CurrentHorizontalOffsetOffset", typeof(double), typeof(AniScrollViewer), new PropertyMetadata(new PropertyChangedCallback(OnHorizontalChanged)));
private static void OnVerticalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AniScrollViewer viewer = d as AniScrollViewer;
viewer.ScrollToVerticalOffset((double)e.NewValue);
}
private static void OnHorizontalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AniScrollViewer viewer = d as AniScrollViewer;
viewer.ScrollToHorizontalOffset((double)e.NewValue);
}
public double CurrentHorizontalOffset
{
get { return (double)this.GetValue(CurrentHorizontalOffsetProperty); }
set { this.SetValue(CurrentHorizontalOffsetProperty, value); }
}
public double CurrentVerticalOffset
{
get { return (double)this.GetValue(CurrentVerticalOffsetProperty); }
set { this.SetValue(CurrentVerticalOffsetProperty, value); }
}
}
Here is an example of the animation code :
private void ScrollToPosition(double x, double y)
{
DoubleAnimation vertAnim = new DoubleAnimation();
vertAnim.From = MainScrollViewer.VerticalOffset;
vertAnim.To = y;
vertAnim.DecelerationRatio = .2;
vertAnim.Duration = new Duration(TimeSpan.FromMilliseconds(250));
DoubleAnimation horzAnim = new DoubleAnimation();
horzAnim.From = MainScrollViewer.HorizontalOffset;
horzAnim.To = x;
horzAnim.DecelerationRatio = .2;
horzAnim.Duration = new Duration(TimeSpan.FromMilliseconds(300));
Storyboard sb = new Storyboard();
sb.Children.Add(vertAnim);
sb.Children.Add(horzAnim);
Storyboard.SetTarget(vertAnim, MainScrollViewer);
Storyboard.SetTargetProperty(vertAnim, new PropertyPath(AniScrollViewer.CurrentVerticalOffsetProperty));
Storyboard.SetTarget(horzAnim, MainScrollViewer);
Storyboard.SetTargetProperty(horzAnim, new PropertyPath(AniScrollViewer.CurrentHorizontalOffsetProperty));
sb.Begin();
}
What am I missing?
Your xaml file needs a reference to your namespace in order to find your AniScrollViewer
Lets say, your AniScrollViewer is located in namespace Test, you can use it in your xaml like so:
<Window x:Class="something"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:Test;assembly=">
<Test:AniScrollViewer />
</Window>
Related
I'm trying to set width of UserControl at initialization time in ViewModel, but it receives only zero. When I resize the window, it receives correct width.
MainWindow.xaml extract:
<local:MapControl x:Name="MapControl"
DataContext="{Binding MapViewModel}"
ActualControlWidth="{Binding ActualControlWidth, Mode=OneWayToSource}" />
MapControl.xaml.cs:
public partial class MapControl : UserControl
{
public MapControl()
{
InitializeComponent();
SizeChanged += OnControlSizeChanged;
}
public static readonly DependencyProperty ActualControlWidthProperty = DependencyProperty.Register(
"ActualControlWidth",
typeof(double),
typeof(MapControl),
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newVar = e.NewValue;
}
public double ActualControlWidth
{
get { return (double)GetValue(ActualControlWidthProperty); }
set { SetValue(ActualControlWidthProperty, value); } //here comes the value as 785
}
private void OnControlSizeChanged(object sender, SizeChangedEventArgs e)
{
ActualControlWidth = ActualWidth;
}
}
MapViewModel.cs:
public class MapViewModel : ViewModelBase
{
private double _actualControlWidth;
private Map _map;
public MapViewModel()
{
...
}
public Map Map
{
get => _map;
set => Set(() => Map, ref _map, value);
}
public double ActualControlWidth
{
get => _actualControlWidth;
set => _actualControlWidth = value; //this is where the value comes as 0
}
}
Thanks for your help!
The full reproduction example is at my Github https://github.com/czechdude/dependencypropertyissue
You need to update this line:
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
To
new FrameworkPropertyMetadata(defaultValue: -1.0D,
new PropertyChangedCallback(MethodToCall));
...
private static void MethodToCall(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// call second method to make instance of MapControl and call a custom method that sets your ActualControlWidth
}
I struggle to get a binding in code behind to a static property.
In WPF I´ve done it as shown below:
<TextBlock Text="{Binding Source={x:Static local:LogListener.Instance}, Path=LogItem.LogType}" Margin="2" />
Now I want to bind "LogItem" against a new dp
#region LogItem
public static readonly DependencyProperty LogItemProperty = DependencyProperty.Register(
"LogItem", typeof(LogItem), typeof(NpLoggerControl),
new PropertyMetadata(default(LogItem), LogItemPropertyChanged));
private static void LogItemPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var type = source as NpLoggerControl;
type?.LogItemPropertyChanged(e);
}
protected virtual void LogItemPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var item = (LogItem) e.NewValue;
}
public LogItem LogItem
{
get { return (LogItem) GetValue(LogItemProperty); }
set { SetValue(LogItemProperty, value); }
}
I´ve no idea how to set it up in Code
You could set the properties just like you did in XAML:
textBlock.SetBinding(
TextBlock.TextProperty,
new Binding
{
Source = LogListener.Instance,
Path = new PropertyPath("LogItem.LogType")
});
Is there any easy way to create a dashed ellipse made up of individual horizontal dashes, where the dash sizes are consistent, and their amount can be specified?
Something like this:
I want to be able to control each dash individually, like changing its color or binding it to an action in my viewmodel.
The only way I can think of to achieve this, is to create a custom control that contains a Path element for each dash, together making up an ellipse shape, having to calculate the Path data based on the amount of dashes and size of the ellipse.
I came back to this problem now, and managed to solve it in a very flexible and generic way. The requirments have changed a bit since then, no need for binding, but it can be added easily.
Note that this is a circle, which is what I wanted. The question should really say circle rather than ellipse, even though a circle is an ellipse, but I digress...
Here's the UserControl I came up with:
StatusRing.xaml.cs
public partial class StatusRing
{
#region Dependency Property registrations
public static readonly DependencyProperty DashesProperty = DependencyProperty.Register("Dashes",
typeof(int), typeof(StatusRing), new PropertyMetadata(32, DashesChanged));
public static readonly DependencyProperty DiameterProperty = DependencyProperty.Register("Diameter",
typeof(double), typeof(StatusRing), new PropertyMetadata(150.00, DiameterChanged));
public static readonly DependencyProperty DashHeightProperty = DependencyProperty.Register("DashHeight",
typeof(double), typeof(StatusRing), new PropertyMetadata(20.00, DashHeightChanged));
public static readonly DependencyProperty DashWidthProperty = DependencyProperty.Register("DashWidth",
typeof(double), typeof(StatusRing), new PropertyMetadata(5.00, DashWidthChanged));
public static readonly DependencyProperty DashFillProperty = DependencyProperty.Register("DashFill",
typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.DimGray, DashFillChanged));
public static readonly DependencyProperty DashAccentFillProperty = DependencyProperty.Register("DashAccentFill",
typeof(SolidColorBrush), typeof(StatusRing), new PropertyMetadata(Brushes.White, DashAnimationFillChanged));
public static readonly DependencyProperty TailSizeProperty = DependencyProperty.Register("TailSize",
typeof(int), typeof(StatusRing), new PropertyMetadata(10, TailSizeChanged));
public static readonly DependencyProperty AnimationSpeedProperty = DependencyProperty.Register("AnimationSpeed",
typeof(double), typeof(StatusRing), new PropertyMetadata(50.00, AnimationSpeedChanged));
public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register("IsPlaying",
typeof(bool), typeof(StatusRing), new PropertyMetadata(false, IsPlayingChanged));
#endregion Dependency Property registrations
private readonly Storyboard glowAnimationStoryBoard = new Storyboard();
public StatusRing()
{
Loaded += OnLoaded;
InitializeComponent();
}
#region Dependency Properties
public int Dashes
{
get => (int)GetValue(DashesProperty);
set => SetValue(DashesProperty, value);
}
public double Diameter
{
get => (double)GetValue(DiameterProperty);
set => SetValue(DiameterProperty, value);
}
public double Radius => Diameter / 2;
public double DashHeight
{
get => (double)GetValue(DashHeightProperty);
set => SetValue(DashHeightProperty, value);
}
public double DashWidth
{
get => (double)GetValue(DashWidthProperty);
set => SetValue(DashWidthProperty, value);
}
public Brush DashFill
{
get => (SolidColorBrush)GetValue(DashFillProperty);
set => SetValue(DashFillProperty, value);
}
public Brush DashAccentFill
{
get => (SolidColorBrush)GetValue(DashAccentFillProperty);
set => SetValue(DashAccentFillProperty, value);
}
public int TailSize
{
get => (int)GetValue(TailSizeProperty);
set => SetValue(TailSizeProperty, value);
}
public double AnimationSpeed
{
get => (double)GetValue(AnimationSpeedProperty);
set => SetValue(AnimationSpeedProperty, value);
}
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set => SetValue(IsPlayingProperty, value);
}
#endregion Dependency Properties
private void OnLoaded(object sender, RoutedEventArgs e)
{
var thisControl = sender as StatusRing;
Recreate(thisControl);
}
#region Dependency Property callbacks
private static void DashesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DiameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void DashAnimationFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void TailSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
Recreate(thisControl);
}
private static void AnimationSpeedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
if (thisControl.IsLoaded)
{
thisControl.glowAnimationStoryBoard.Stop();
thisControl.glowAnimationStoryBoard.Children.Clear();
ApplyAnimations(thisControl);
thisControl.glowAnimationStoryBoard.Begin();
}
}
private static void IsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as StatusRing;
if (thisControl.IsLoaded)
{
var isPlaying = (bool)e.NewValue;
if (isPlaying)
{
thisControl.glowAnimationStoryBoard.Begin();
}
else
{
thisControl.glowAnimationStoryBoard.Stop();
}
}
}
#endregion Dependency Property callbacks
private static void Recreate(StatusRing thisControl)
{
if (thisControl.IsLoaded)
{
thisControl.glowAnimationStoryBoard.Stop();
thisControl.glowAnimationStoryBoard.Children.Clear();
thisControl.RootCanvas.Children.Clear();
Validate(thisControl);
BuildRing(thisControl);
ApplyAnimations(thisControl);
if (thisControl.IsPlaying)
{
thisControl.glowAnimationStoryBoard.Begin();
}
else
{
thisControl.glowAnimationStoryBoard.Stop();
}
}
}
private static void Validate(StatusRing thisControl)
{
if (thisControl.TailSize > thisControl.Dashes)
{
throw new Exception("TailSize cannot be larger than amount of dashes");
}
}
private static void BuildRing(StatusRing thisControl)
{
var angleStep = (double)360 / thisControl.Dashes;
for (double i = 0; i < 360; i = i + angleStep)
{
var rect = new Rectangle
{
Fill = thisControl.DashFill,
Height = thisControl.DashHeight,
Width = thisControl.DashWidth
};
//Rotate dash to follow circles circumference
var centerY = thisControl.Radius;
var centerX = thisControl.DashWidth / 2;
var rotateTransform = new RotateTransform(i, centerX, centerY);
rect.RenderTransform = rotateTransform;
var offset = thisControl.Radius - thisControl.DashWidth / 2;
rect.SetValue(Canvas.LeftProperty, offset);
thisControl.RootCanvas.Children.Add(rect);
}
thisControl.RootCanvas.Width = thisControl.Diameter;
thisControl.RootCanvas.Height = thisControl.Diameter;
}
private static void ApplyAnimations(StatusRing thisControl)
{
var baseColor = ((SolidColorBrush)thisControl.DashFill).Color;
var animatedColor = ((SolidColorBrush)thisControl.DashAccentFill).Color;
var dashes = thisControl.RootCanvas.Children.OfType<Rectangle>().ToList();
double animationPeriod = thisControl.AnimationSpeed;
double glowDuration = animationPeriod * thisControl.TailSize;
for (int i = 0; i < dashes.Count; i++)
{
var beginTime = TimeSpan.FromMilliseconds(animationPeriod * i);
var colorAnimation = new ColorAnimationUsingKeyFrames
{
BeginTime = beginTime,
RepeatBehavior = RepeatBehavior.Forever
};
var toFillColor = new LinearColorKeyFrame(animatedColor, TimeSpan.Zero);
colorAnimation.KeyFrames.Add(toFillColor);
var dimToBase = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(glowDuration));
colorAnimation.KeyFrames.Add(dimToBase);
var restingTime = animationPeriod * dashes.Count;
var delay = new LinearColorKeyFrame(baseColor, TimeSpan.FromMilliseconds(restingTime));
colorAnimation.KeyFrames.Add(delay);
Storyboard.SetTarget(colorAnimation, dashes[i]);
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("(Fill).(SolidColorBrush.Color)"));
thisControl.glowAnimationStoryBoard.Children.Add(colorAnimation);
}
}
}
StatusRing.xaml:
<UserControl x:Class="WpfPlayground.StatusRing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Canvas x:Name="RootCanvas" />
Usage:
<local:StatusRing Diameter="250"
Dashes="32"
TailSize="16"
IsPlaying="True" />
Result:
The number of dashes, length and speed of animation, etc... are all configurable. The naming of the dependency properties could be better though...
Enjoy :-)
I have a class DependencyReportViewer that is correspond for creation a ReportViewer control in my program. This class works perfectly. In XAML we define only this line for each report that we wants:
<ctr:DependencyReportViewer EmbeddedReportName="Program.View.Reports.ReportName.rdlc" ReportData="{Binding ReportData}"/>
First part of the class:
<WindowsFormsHost x:Class="Program.View.Controls.DependencyReportViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
IsVisibleChanged="WindowsFormsHost_IsVisibleChanged">
</WindowsFormsHost>
And the worst part of the class:
public partial class DependencyReportViewer : WindowsFormsHost
{
private static ReportViewer ReportViewer = new ReportViewer();
private static int ReportIsRunning = 0;
static DependencyReportViewer()
{
DependencyReportViewer.ReportViewer.SetDisplayMode(DisplayMode.PrintLayout);
try
{
RenderingExtensions = ReportViewer.LocalReport.ListRenderingExtensions();
}
catch (Exception)
{
RenderingExtensions = new RenderingExtension[0];
}
ReportViewer.ReportError += ReportViewer_ReportError;
}
public static RenderingExtension[] RenderingExtensions { get; private set; }
public static readonly RoutedEvent ReportDoneEvent = EventManager.RegisterRoutedEvent("ReportDone", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(DependencyReportViewer));
public event RoutedEventHandler ReportDone
{
add { AddHandler(ReportDoneEvent, value); }
remove { RemoveHandler(ReportDoneEvent, value); }
}
public string EmbeddedReportName
{
get { return (string)GetValue(EmbeddedReportNameProperty); }
set { SetValue(EmbeddedReportNameProperty, value); }
}
public static readonly DependencyProperty EmbeddedReportNameProperty = DependencyProperty.Register("EmbeddedReportName",
typeof(string), typeof(DependencyReportViewer));
public Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>> ReportData
{
get { return (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)GetValue(ReportDataProperty); }
set { SetValue(ReportDataProperty, value); }
}
public static readonly DependencyProperty ReportDataProperty = DependencyProperty.Register("ReportData",
typeof(Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>), typeof(DependencyReportViewer),
new FrameworkPropertyMetadata(null, ReportData_Changed));
private static void ReportData_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sts = Interlocked.CompareExchange(ref ReportIsRunning, 1, 0);
if (sts != 0)
return;
var rv = (DependencyReportViewer)d;
var data = (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)e.NewValue;
RenderingCompleteEventHandler evt = null;
evt = (sender, rea) =>
{
ReportViewer.RenderingComplete -= evt;
sts = Interlocked.Exchange(ref ReportIsRunning, 0);
if (sts == 1)
rv.RaiseEvent(new RoutedEventArgs(ReportDoneEvent));
};
ReportViewer.RenderingComplete += evt;
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
if (data != null)
{
DependencyReportViewer.ReportViewer.LocalReport.SetBasePermissionsForSandboxAppDomain(new System.Security.PermissionSet(System.Security.Permissions.PermissionState.Unrestricted));
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = rv.EmbeddedReportName;
var l = new List<ReportParameter>();
foreach (var item in data.Item2)
{
l.Add(new ReportParameter(item.Item1, item.Item2));
}
DependencyReportViewer.ReportViewer.LocalReport.SetParameters(l);
foreach (var item in data.Item1)
{
var rds = new ReportDataSource(item.Item1);
rds.Value = item.Item2;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Add(rds);
}
}
DependencyReportViewer.ReportViewer.RefreshReport();
}
private static void ReportViewer_ReportError(object sender, ReportErrorEventArgs e)
{
Interlocked.Exchange(ref ReportIsRunning, 0);
}
public DependencyReportViewer()
{
InitializeComponent();
}
private void WindowsFormsHost_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
this.Child = DependencyReportViewer.ReportViewer;
}
else
{
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Clear();
DependencyReportViewer.ReportViewer.RefreshReport();
this.Child = null;
}
}
}
The problem is that this code works normally only in situations, when we have only one UserControl with this ReportViewer opened. If we open two or more controls, then the last open control will take over ReportViewer. In other words it is possible to open only one ReportViewer at the same time.
Now I want to have a possibility to open multiple controls with multiple report viewers. As I get the problem is that this class is static. And when we define a new control, then in "this.Child = DependencyReportViewer.ReportViewer;" line we just reset this viewer to the newest control.
But the man who wrote this had a skill much greater then I have now. I failed multiple times to set this class nonstatic with XamlParseException. How can I convert this class to nonstatic?
Fellow WPF users,
I'm currently trying to wrap my head around a problem related to WPF Blend behaviors. I've found a behavior I'd like to use in a WPF application, but the code was originally written for Silverlight some time ago (http://www.sharpgis.net/post/2009/08/11/Silverlight-Behaviors-Triggers-and-Actions.aspx).
The problem I have is that when the animation starts and the dependency property OffsetMediatorProperty changes, and I get into the method OnOffsetMediatorPropertyChanged, the AssociatedObject is null. In addition, it also looks like all the fields are null.
The behavior I'm using:
public class MouseScrollViewer : Behavior<ScrollViewer>
{
double target = 0;
int direction = 0;
private Storyboard storyboard;
private DoubleAnimation animation;
protected override void OnAttached()
{
base.OnAttached();
CreateStoryBoard();
AssociatedObject.PreviewMouseWheel += AssociatedObject_MouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_MouseWheel;
storyboard = null;
base.OnDetaching();
}
private void AssociatedObject_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (Animate(-Math.Sign(e.Delta) * ScrollAmount))
{
e.Handled = true;
}
}
private bool Animate(double offset)
{
storyboard.Pause();
if (Math.Sign(offset) != direction)
{
target = AssociatedObject.VerticalOffset;
direction = Math.Sign(offset);
}
target += offset;
target = Math.Max(Math.Min(target, AssociatedObject.ScrollableHeight), 0);
animation.To = target;
animation.From = AssociatedObject.VerticalOffset;
if (animation.From != animation.To)
{
storyboard.Begin();
return true;
}
return false;
}
private void CreateStoryBoard()
{
storyboard = new Storyboard();
animation = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(.5),
EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath(OffsetMediatorProperty));
storyboard.Children.Add(animation);
storyboard.Completed += (s, e) => { direction = 0; };
}
internal double OffsetMediator
{
get { return (double)GetValue(OffsetMediatorProperty); }
set { SetValue(OffsetMediatorProperty, value); }
}
internal static readonly DependencyProperty OffsetMediatorProperty =
DependencyProperty.Register("OffsetMediator", typeof(double), typeof(MouseScrollViewer), new PropertyMetadata(0.0, OnOffsetMediatorPropertyChanged));
private static void OnOffsetMediatorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MouseScrollViewer msv = d as MouseScrollViewer;
if (msv != null && msv.AssociatedObject != null)
{
msv.AssociatedObject.ScrollToVerticalOffset((double)e.NewValue);
}
}
public double ScrollAmount
{
get { return (double)GetValue(ScrollAmountProperty); }
set { SetValue(ScrollAmountProperty, value); }
}
public static readonly DependencyProperty ScrollAmountProperty =
DependencyProperty.Register("ScrollAmount", typeof(double), typeof(MouseScrollViewer), new PropertyMetadata(50.0));
}
Usage:
<ScrollViewer>
<i:Interaction.Behaviors>
<local:MouseScrollViewer x:Name="ScrollViewer" />
</i:Interaction.Behaviors>
<ItemsControl ItemsSource="{Binding}"
FontSize="32">
</ItemsControl>
</ScrollViewer>
It looks like the behavior works in the Silverlight demo, but not in a WPF application. I really hope some of you are able to explain me why this happens, and hopefully help me sort out this issue.
When storyboard is run using Begin(), it internally tries to access freezable animation object which in your case is instance object of class MouseScrollViewer and hence it ends up cloning an instance of animation and eventually your MosueScrollViewer.
So, actual issue is AssociatedObject is null because animation is done on another instance of MouseScrollViewer and not on your actual instance.
There are two workarounds for this:
First call animation.Freeze() on animation object so that new instance is not created but issue with this approach is it will work first time only and second time it will fail with that freeze objects properties can't be modified.
Second workaround would be to, you don't need storyboard at all when you need animation from code. You can directly do animation on your current instance. Look below for the changes you need to make to do that:
First of all remove the storyboard completely from your code.
Second modify CreateStoryBoard() to CreateAnimation():
private void CreateAnimation()
{
animation = new DoubleAnimation
{
Duration = TimeSpan.FromSeconds(.5),
EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new
PropertyPath(OffsetMediatorProperty));
animation.Completed += (s, e) => { direction = 0; };
}
and Animate() method should go like this:
private bool Animate(double offset)
{
if (Math.Sign(offset) != direction)
{
target = AssociatedObject.VerticalOffset;
direction = Math.Sign(offset);
}
target += offset;
target = Math.Max(Math.Min(target, AssociatedObject.ScrollableHeight), 0);
animation.To = target;
animation.From = AssociatedObject.VerticalOffset;
if (animation.From != animation.To)
{
this.BeginAnimation(OffsetMediatorProperty, animation); <-- HERE
return true;
}
return false;
}