Animate (smoothly) ScrollViewer programmatically - c#

Is there a way to smoothly animate a ScrollViewers vertical offset in Windows Phone 8.1 Runtime?
I have tried using the ScrollViewer.ChangeView() method and the change of vertical offset is not animated no matter if I set the disableAnimation parameter to true or false.
For example: myScrollViewer.ChangeView(null, myScrollViewer.VerticalOffset + p, null, false);
The offset is changed without animation.
I also tried using a vertical offset mediator:
/// <summary>
/// Mediator that forwards Offset property changes on to a ScrollViewer
/// instance to enable the animation of Horizontal/VerticalOffset.
/// </summary>
public sealed class ScrollViewerOffsetMediator : FrameworkElement
{
/// <summary>
/// ScrollViewer instance to forward Offset changes on to.
/// </summary>
public ScrollViewer ScrollViewer
{
get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
set { SetValue(ScrollViewerProperty, value); }
}
public static readonly DependencyProperty ScrollViewerProperty =
DependencyProperty.Register("ScrollViewer",
typeof(ScrollViewer),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(null, OnScrollViewerChanged));
private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
var scrollViewer = (ScrollViewer)(e.NewValue);
if (null != scrollViewer)
{
scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
}
}
/// <summary>
/// VerticalOffset property to forward to the ScrollViewer.
/// </summary>
public double VerticalOffset
{
get { return (double)GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.Register("VerticalOffset",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
if (null != mediator.ScrollViewer)
{
mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
}
}
/// <summary>
/// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
/// </summary>
/// <remarks>
/// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
/// </remarks>
public double ScrollableHeightMultiplier
{
get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
set { SetValue(ScrollableHeightMultiplierProperty, value); }
}
public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
DependencyProperty.Register("ScrollableHeightMultiplier",
typeof(double),
typeof(ScrollViewerOffsetMediator),
new PropertyMetadata(0.0, OnScrollableHeightMultiplierChanged));
public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var mediator = (ScrollViewerOffsetMediator)o;
var scrollViewer = mediator.ScrollViewer;
if (null != scrollViewer)
{
scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
}
}
}
and I can animate the VerticalOffset property with DoubleAnimation:
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
da.EnableDependentAnimation = true;
da.From = Mediator.ScrollViewer.VerticalOffset;
da.To = da.From + p;
da.Duration = new Duration(TimeSpan.FromMilliseconds(300));
da.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
Storyboard.SetTarget(da, Mediator);
Storyboard.SetTargetProperty(da, "(Mediator.VerticalOffset)");
sb.Children.Add(da);
sb.Begin();
Mediator is declared in XAML.
But this animation is not smooth on my device (Lumia 930).

You should stick with ChangeView for scrolling animations regardless of whether data virtualization is on or not.
Without seeing your code where the ChangeView doesn't work, it's a bit hard to guess what's really going on but there are a couple of things that you can try.
First approach is to add a Task.Delay(1) before calling ChangeView, just to give the OS some time to finish off other concurrent UI tasks.
await Task.Delay(1);
scrollViewer.ChangeView(null, scrollViewer.ScrollableHeight, null, false);
The second approach is a bit more complex. What I've noticed is that, when you have many complex items in the ListView, the scrolling animation from the first item to the last (from the ChangeView method) isn't very smooth at all.
This is because the ListView first needs to realize/render many items along the way due to data virtualization and then does the animated scrolling. Not very efficient IMHO.
What I came up with is this - First, use a non-animated ListView.ScrollIntoView to scroll to the last item just to get it realized. Then, call ChangeView to move the offset up to a size of the ActualHeight * 2 of the ListView with animation disabled (you can change it to whatever size you want based on your app's scrolling experience). Finally, call ChangeView again to scroll back to the end, with animation this time. Doing this will give a much better scrolling experience 'cause the scrolling distance is just the ActualHeight of the ListView.
Keep in mind that when the item you want to scroll to is already realized on the UI, you don't want to do anything above. You simply just calculate the distance between this item and the top of the ScrollViewer and call ChangeView to scroll to it.
I already wrapped the logic above in this answer's Update 2 section (thanks to this question I realized my initial answer doesn't work when virtualization is on :p). Let me know how you go.

I think that question has already been answered here:
Animated (Smooth) scrolling on ScrollViewer
There is also the WinRT XAML Toolki, which provides "a way to scroll a ScrollViewer to specified offset with animation":
http://winrtxamltoolkit.codeplex.com/

With ScrollToVerticalOffset deprecated/obsolete in newer builds of Windows 10 (leaving the ScrollViewOffSetMediator extension control no longer working), and the new ChangeView method not actually providing smooth or controllable animation, a new solution is needed. Please see my answer here which allows one to smoothly animate and zoom the ScrollViewer and its contents to any desired position, regardless of where the application's end user has the scrollbars initially positioned:
How to scroll to element in UWP

I believe this article is what you're looking for and it seems the method he used is working for you.
Quick Way:
Add offset dependency parameter manually to scrollviewer.
Duplicate your scrollviewer
Use an animator.

Related

How should I "pause" the program to show a tip to the user?

When a user is running my program for the first time, I want them to go through a series of tips. Each time they hit a certain "checkpoint", the program will pause what its doing, the background will go a little fuzzy (except for the area of the window the tip is referencing), and a tip will appear on top, explaining how to use it / what to do etc.
I dont quite know what to call this, in my head its called "tutorial tips", but googling anything related to this shows a load of generic tutorials with WPF / C#.
What would be the best way to do this? Am I really just looking at using popups and controling when they are visible? Is there a better / more elegant solution, or any resources out there to aid with this?
Ok, I think I may have dedicated too much time to this, but it sounded like a cool challenge :P
I've created a Decorator class named TipFocusDecorator that handles all this.
public class TipFocusDecorator : Decorator
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
// Using a DependencyProperty as the backing store for Open. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(TipFocusDecorator),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsOpenPropertyChanged));
public string TipText
{
get { return (string)GetValue(TipTextProperty); }
set { SetValue(TipTextProperty, value); }
}
// Using a DependencyProperty as the backing store for TipText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TipTextProperty =
DependencyProperty.Register("TipText", typeof(string), typeof(TipFocusDecorator), new UIPropertyMetadata(string.Empty));
public bool HasBeenShown
{
get { return (bool)GetValue(HasBeenShownProperty); }
set { SetValue(HasBeenShownProperty, value); }
}
// Using a DependencyProperty as the backing store for HasBeenShown. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasBeenShownProperty =
DependencyProperty.Register("HasBeenShown", typeof(bool), typeof(TipFocusDecorator), new UIPropertyMetadata(false));
private static void IsOpenPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var decorator = sender as TipFocusDecorator;
if ((bool)e.NewValue)
{
if (!decorator.HasBeenShown)
decorator.HasBeenShown = true;
decorator.Open();
}
if (!(bool)e.NewValue)
{
decorator.Close();
}
}
TipFocusAdorner adorner;
protected void Open()
{
adorner = new TipFocusAdorner(this.Child);
var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
adornerLayer.Add(adorner);
MessageBox.Show(TipText); // Change for your custom tip Window
IsOpen = false;
}
protected void Close()
{
var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
adornerLayer.Remove(adorner);
adorner = null;
}
}
This Decorator must be used in XAML around the control you want to focus. It has three properties: IsOpen, TipText and HasBeenShown. IsOpen must be set to true to make the focus and tip window appear (and is set to false automatically when the tip window is closed). TipText allows you to define the text that must be shown in the tip window. And HasBeenShown keeps track of whether the tip window has been shown, so it only shows once. You can use Bindings for all these properties or set them from code-behind.
To create the focus effect, this class uses another custom Adorner, the TipFocusAdorner:
public class TipFocusAdorner : Adorner
{
public TipFocusAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var root = Window.GetWindow(this);
var adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
var presentationSource = PresentationSource.FromVisual(adornerLayer);
Matrix transformToDevice = presentationSource.CompositionTarget.TransformToDevice;
var sizeInPixels = transformToDevice.Transform((Vector)adornerLayer.RenderSize);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)(sizeInPixels.X), (int)(sizeInPixels.Y), 96, 96, PixelFormats.Default);
var oldEffect = root.Effect;
var oldVisibility = AdornedElement.Visibility;
root.Effect = new BlurEffect();
AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, Visibility.Hidden);
rtb.Render(root);
AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, oldVisibility);
root.Effect = oldEffect;
drawingContext.DrawImage(rtb, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(22, 0, 0, 0)), null, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
drawingContext.DrawRectangle(new VisualBrush(AdornedElement) { AlignmentX = AlignmentX.Left, TileMode = TileMode.None, Stretch = Stretch.None },
null,
AdornedElement.RenderTransform.TransformBounds(new Rect(AdornedElement.RenderSize)));
}
}
This dims and blurs (and freezes, since it actually uses a screen capture) all the window, while keeping the desired controls focused and clear (and moving - i.e. in TextBoxes, the text input caret will still be visible and blinking).
To use this Decorator, you only must set it like this in XAML:
<StackPanel>
<local:TipFocusDecorator x:Name="LoginDecorator"
TipText="Enter your username and password and click 'Login'"
IsOpen="{Binding ShowLoginTip}">
<local:LoginForm />
</local:TipFocusDecorator>
</StackPanel>
And the final result, when ShowLoginTip is set to true:
KNOWN ISSUES
Right now this uses a simple MessageBox to show the tip, but you can create your own Window class for the tips, style it as you want, and call it with ShowDialog() instead of the MessageBox.Show() (and you could also control where the Window appears, if you want it to appear right next to the focused Control or something like that).
Also, this won't work inside UserControls right away, because AdornerLayer.GetAdornerLayer(AdornedElement) will return null inside UserControls. This could be easily fixed by looking for the AdornerLayer of the PARENT of the UserControl (or the parent of the parent, recursively). There are functions around to do so.
This won't work for Pages either, only for Windows. Simply because I use Window.GetWindow(this) to get the parent Window of the Decorator... You could use other functions to get the parent, that could work either with Windows, Pages or whatever. As with the AdornerLayer problem, there are plenty of solutions for this around here.
Also, I guess this could be animated somehow (making the blur and dim effect appear gradually, for instance), but haven't really looked into it...
You can create your tip as a window and show it using ShowDialog(). This gives you a Modal dialog, as others have suggested. Be sure to set it's owner. Just before you show it, you can use
<UIElement.Effect>
<BlurEffect/>
</UIelement.Effect>
to set your window or outer container's(grid maybe) Blur Effect. The radius property sets the "level" of blur, so I imagine you can set it to 0 initially and modify it programatically when you show your dialog

Animation Methods, Simplification and Repairing

Context
I'm kinda new at animating WPF stuff, but I've played around with a library or two and I "had" an animation that I used with the Window control in WPF, this is an example of that method, keep in mind that this method works:
public void AnimateFadeWindow(object sender, double opacity, double period)
{
//Tab item is a enw tab item (the sender is casted to this type.)
Window win = (Window)sender;
win.Opacity = 0;
//using the doubleanimation class, animation is a new isntancem use the parameter opacity and set the period to a timespan.
DoubleAnimation animation = new DoubleAnimation(opacity, TimeSpan.FromSeconds(period));
//begin the animation on the object.
win.BeginAnimation(Window.OpacityProperty, animation);
}
Problem
Like I said previously, This code works, the problem with this code was, of course, it's only suited to the Window control, it won't work with other controls, for instance, TabItem, Button or any other control I wanted to use it for, so I "Upgraded" my method and this is my CURRENT method:
public void AnimateFade(object sender, double opacity, double period)
{
//using the doubleanimation class, animation is a new isntancem use the parameter opacity and set the period to a timespan.
DoubleAnimation animation = new DoubleAnimation(opacity, TimeSpan.FromSeconds(period));
Object obj = sender.GetType();
if (obj is TabItem)
{
TabItem tab = (TabItem)sender;
tab.BeginAnimation(TabItem.OpacityProperty, animation);
}
else if (obj is Label)
{
Label lab = (Label)sender;
lab.BeginAnimation(Label.OpacityProperty, animation);
}
else if (obj is Window)
{
Window win = (Window)sender;
win.Opacity = 0;
win.BeginAnimation(Window.OpacityProperty, animation);
}
}
This method doesn't work. I don't really know what I'm doing wrong here, so I wondered if someone could possibly help out.
ALSO, is there an easier way to do this using something like the PropertyInfo class or a reflection class?
Thanks Stack.
Your issue is nothing to do with Animation.The problem is you are comparing sender.Type while you should compare sender itself i.e.
use if (sender is TabItem) instead of if (obj is TabItem).
Moreover, There is no need to compare sender with TabItem, Lable, Window and etc one by one, they are all UIElements! and Since UIElement implements IAnimatable, you just need to cast sender to UIElement and you have a general method that applies your animation to any control :
public void AnimateFade(object sender, double opacity, double period)
{
UIElement element = (UIElement)sender;
element.Opacity = 0;
DoubleAnimation animation = new DoubleAnimation(opacity, TimeSpan.FromSeconds(period));
element.BeginAnimation(UIElement.OpacityProperty, animation);
}

WPF- Horizontal and Vertical position of a Run Element in a FlowDocument

Is there anyway to get the horizontal position(pixel) and vertical position(pixel) of a Run element in a FlowDocument?
Edit:
All i need to do is scroll to that position and make it the top line of the FlowDocument.
To Answer Your Question
The code needed to get the position of a content element in a document is all internal to .NET and not publically exposed. You would need access to an IContentHost implementation, which the built-in document viewers do not publically expose. So, there is no supported way to do what you are asking.
To Solve Your Actual Problem
There is a way to achieve your desired result of scrolling the element to the top of the view. What you want to do is scroll to the end of the document, then call BringIntoView on the element you want to have at the top.
There are multiple ways a FlowDocument can be displayed in an application. How you handle the scrolling depends on which control you are using to present the FlowDocument.
In a RichTextBox, use the ScrollToEnd method.
In a FlowDocumentScrollViewer, you will need to get its internal ScrollViewer and call ScrollToBottom on it. (You have to wait until the control is loaded before you can get a template part from it.)
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
mScrollViewer = mViewer.Template.FindName("PART_ContentHost", mViewer) as ScrollViewer;
}
In a FlowDocumentReader, the process is a bit more complex.
When the control is loaded, register for changes to the ViewingMode property and run the handler once to account for the starting value:
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(FlowDocumentReader.ViewingModeProperty, typeof(FlowDocumentReader));
descriptor.AddValueChanged(mReader, (s, a) => Reader_ViewModeChanged());
Reader_ViewModeChanged();
}
In the handler, dig in to find the ScrollViewer. It will only be present when the ViewingMode is set to Scroll:
private void Reader_ViewModeChanged()
{
mScrollViewer = null;
if (mReader.ViewingMode == FlowDocumentReaderViewingMode.Scroll)
{
var contentHost = mReader.Template.FindName("PART_ContentHost", mReader) as DependencyObject;
if (contentHost != null && VisualTreeHelper.GetChildrenCount(contentHost) > 0)
{
var documentScrollViewer = VisualTreeHelper.GetChild(contentHost, 0) as FlowDocumentScrollViewer;
if (documentScrollViewer != null)
{
documentScrollViewer.ApplyTemplate();
mScrollViewer = documentScrollViewer.Template.FindName("PART_ContentHost", documentScrollViewer) as ScrollViewer;
}
}
}
}
Once you have the ScrollViewer, you can call ScrollToBottom on it when desired.
Now, scroll to the bottom of the document, then call BringIntoView on your Run, and it should be at the top of the view.
Does not bring it to the top but just call BringIntoView on the Run. Save a reference to the Run.
It may be late but i still want to share the way i DID it in WPF.
You need an offset to do so.
As the above said: Flow gave you:
flow.ScrollToHome(); // Bottom
But also gave: ScrollToVerticalOffset (get from Rect)
if you have index (offset of the char/line) - you can find it in you saved data or get the TextPointer with flow.Selection.Start/End
TextPointer t_st = flow.Selection.Start;
double offset = flow.Document.ContentStart.GetOffsetToPosition(t_st);
private void gotoOffset(double offset)
{
TextPointer myTextPointer1 = flow.Document.ContentStart.GetPositionAtOffset((int)offset);
flow.Selection.Select(myTextPointer1, myTextPointer1);
flow.Focus();
Rect screenPos2 = myTextPointer1.GetCharacterRect(LogicalDirection.Forward);
double offset2 = screenPos2.Top;
Thread.Sleep(100);
flow.ScrollToVerticalOffset(offset2);
flow.Focus();
}
As the code above, We get the Rect from TextPointer, the Textpointer and get from Offset.
The focus just to make sure to place the cursor in right place.
Sometime the issue happen when you jump to many offset.
I recomment to trigger flow.ScrollToHome(); Before jump (because this ScrollToVerticalOffset true from the start, not any line)

Change background of the thumb of Trackbar

Is any way to change the background of the thumb of Trackbar? I use C# winform.
Only the background of the Trackbar can be changed, so how can I change or set something in the thumb?
Any answer appreciated.
I saw the project:
http://www.codeproject.com/Articles/7974/A-variation-on-the-default-TrackBar
And add the Picture of it to let user choose:
/// <summary>
/// Set Image of Trackbar's thumb
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[Description("Set the picture of the thumb")]
[Category("Image")]
public Image Picture
{
set
{
myPic = value;
}
get { return myPic; }
}
And use TextureBrush to let the picture People shoose be an ImageBrush:
TextureBrush newBrush = new TextureBrush(myPic);
if (myPic != null)
{
g.FillRegion(newBrush, new Region(PaintPath));
newBrush.Dispose();
}
else
{
g.FillRegion(bgBrush, new Region(PaintPath));
bgBrush.Dispose();
}
There is only one problem that the imagebrush is tile style,
if I let it not a tile style, it may resolve my questrion.

How can I make a WPF Image disableable?

I need an Image that is grayed out when disabled (IsEnabled=False). A grayed out version of the image can be produced by reading the BitmapImage into a FormatConvertedBitmap which is shown here.
I have been able to get this working with a UserControl but now I would like the same behavior in a specialized Image class for more flexibility. I don't care if this is implemented in XAML, code-behind or both, but it needs to be a subclass of Image.
The usage could be:
<DisableableImage Source="Images/image1.png" />
<DisableableImage Source="Images/image1.png" IsEnabled="False" />
<!-- Since IsEnabled is inherited down the tree,
the image will be grayed out like the rest of the button -->
<Button IsEnabled="False">
<StackPanel Orientation="Horizontal">
<TextBlock>OK</TextBlock>
<DisableableImage Source="Images/ok.png" />
</StackPanel>
</Button>
Have a look at this link
EDIT:
Or this one (all you need is the AutoGreyableImage class)
I made a little comparison based on the following solutions.
The approaches in the link provided by the OP
The links provided by Thomas Levesque
AutoDisabledImage
AutoGreyableImage
Greyscale Effect
Since I already had a licens for the Infragistics Net Advantage for WPF it was easy to try it out
Here is the result
So the best approach depends on what results you are after. As for me, I think the result produced by AutoDisabledImage from Infragistics is too bright, AutoGreyableImage does a pretty good job (Identical result to Approach 1 (OP link)) and GreyscaleEffect produces the best result.
if you use this a lot consider creating a custom Effect introduced with .NET 3.5 SP1 (not bitmapeffect) to render such an operation on your GPU. this effect can then be easily controlled by triggers.
More complete version of the AutoGreyableImage by Thomas Lebrun. For anyone interested, I started using Thomas Lebruns class and ran into a couple of nullreference exceptions, as well as finding out that an image would not be disabled if the isEnabled property was set first and the source set after.
So here's the class that finally did the trick for me. À propos, you can of course add the matter of opacity into this, but I decided to leave that up to the xaml around the image.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace MyDisabledImages
{
/// <summary>
/// Class used to have an image that is able to be gray when the control is not enabled.
/// Based on the version by Thomas LEBRUN (http://blogs.developpeur.org/tom)
/// </summary>
public class AutoGreyableImage : Image
{
/// <summary>
/// Initializes a new instance of the <see cref="AutoGreyableImage"/> class.
/// </summary>
static AutoGreyableImage()
{
// Override the metadata of the IsEnabled and Source property.
IsEnabledProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAutoGreyScaleImageIsEnabledPropertyChanged)));
SourceProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnAutoGreyScaleImageSourcePropertyChanged)));
}
protected static AutoGreyableImage GetImageWithSource(DependencyObject source)
{
var image = source as AutoGreyableImage;
if (image == null)
return null;
if (image.Source == null)
return null;
return image;
}
/// <summary>
/// Called when [auto grey scale image source property changed].
/// </summary>
/// <param name="source">The source.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
protected static void OnAutoGreyScaleImageSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs ars)
{
AutoGreyableImage image = GetImageWithSource(source);
if (image != null)
ApplyGreyScaleImage(image, image.IsEnabled);
}
/// <summary>
/// Called when [auto grey scale image is enabled property changed].
/// </summary>
/// <param name="source">The source.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
protected static void OnAutoGreyScaleImageIsEnabledPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
AutoGreyableImage image = GetImageWithSource(source);
if (image != null)
{
var isEnabled = Convert.ToBoolean(args.NewValue);
ApplyGreyScaleImage(image, isEnabled);
}
}
protected static void ApplyGreyScaleImage(AutoGreyableImage autoGreyScaleImg, Boolean isEnabled)
{
try
{
if (!isEnabled)
{
BitmapSource bitmapImage = null;
if (autoGreyScaleImg.Source is FormatConvertedBitmap)
{
// Already grey !
return;
}
else if (autoGreyScaleImg.Source is BitmapSource)
{
bitmapImage = (BitmapSource)autoGreyScaleImg.Source;
}
else // trying string
{
bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));
}
FormatConvertedBitmap conv = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0);
autoGreyScaleImg.Source = conv;
// Create Opacity Mask for greyscale image as FormatConvertedBitmap does not keep transparency info
autoGreyScaleImg.OpacityMask = new ImageBrush(((FormatConvertedBitmap)autoGreyScaleImg.Source).Source); //equivalent to new ImageBrush(bitmapImage)
}
else
{
if (autoGreyScaleImg.Source is FormatConvertedBitmap)
{
autoGreyScaleImg.Source = ((FormatConvertedBitmap)autoGreyScaleImg.Source).Source;
}
else if (autoGreyScaleImg.Source is BitmapSource)
{
// Should be full color already.
return;
}
// Reset the Opcity Mask
autoGreyScaleImg.OpacityMask = null;
}
}
catch (Exception)
{
// nothin'
}
}
}
}
Create a DisableableImage class that is a typical WPF control. Inside, place two elements: the image, and a rectangle that appears only when the control is disabled. The rectangle should be the same width and height as the image, and it should overlay the image. With a color of gray and an alpha of somewhere around 40%, you should get an effect similar to actually graying out the image -- without all the effort to actually modify the image itself.

Categories

Resources