DrawingVisual is not refreshed - wpf

I create my own FrameworkElement and override VisualChildrenCount{get;} and GetVisualChild(int index) by returning my own DrawingVisual instance.
If I modify the content of the visual after initial rendering (e.g. in timer handler) using DrawingVisual.RenderOpen() and drawing into the context, the element is not refreshed.
Here's the simplest sample:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
namespace VisualTest
{
public class TestControl : FrameworkElement
{
private readonly DrawingVisual _visual = new DrawingVisual();
public TestControl()
{
Draw(false);
var timer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 2)};
timer.Tick += (sender, args) =>
{
Draw(true);
InvalidateVisual();
timer.Stop();
};
timer.Start();
}
protected override Visual GetVisualChild(int index)
{
return _visual;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
private void Draw(bool second)
{
DrawingContext ctx = _visual.RenderOpen();
if (!second)
ctx.DrawRoundedRectangle(Brushes.Green, null, new Rect(0, 0, 200, 200), 20, 20);
else
ctx.DrawEllipse(Brushes.Red, null, new Point(100, 100), 100, 100);
ctx.Close();
}
}
}
InvalidateVisual() does nothing. Although if you resize the window containing the element, it gets updated.
Any ideas on how to properly refresh the content? Preferably without introducing new dependency properties for my element.

Add
this.AddVisualChild(_visual);
this.AddLogicalChild(_visual);
to TestControl class constructor.

Based on SMART_n's answer, here is an improved solution that doesn't leak memory:
public TestControl()
{
Loaded += AddVisualToTree;
Unloaded += RemoveVisualFromTree;
Draw(false);
var timer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 2)};
timer.Tick += (sender, args) =>
{
Draw(true);
InvalidateVisual();
timer.Stop();
};
timer.Start();
}
private void AddVisualToTree(object sender, RoutedEventArgs e)
{
AddVisualChild(_visual);
AddLogicalChild(_visual);
}
private void RemoveVisualFromTree(object sender, RoutedEventArgs e)
{
RemoveLogicalChild(_visual);
RemoveVisualChild(_visual);
}

If you make _visual a DrawingGroup, you can re-open it later and change it's drawing commands, and they will be updated.

Related

How to draw dropshadow effect in a geometry in WPF

I'm drawing the following Shape in a Canvas.
I would like to highlight it when it's selected by changing its color (the easy part) and drawing an small halo around it:
This is how I did using SASS: http://codepen.io/aaromnido/pen/zKvAwd/
How coud I draw in WPF? Remember that I'm drawing using the Shape's OnRender method.
Set some defaults in constructor.
One of these defaults is Shape.Effect, as it will be animated on MouseEnter event.
Construct VisualStates for Normal , and MouseEnter scenarios.
Change the VisualState of the element using VisualStateManager.GoToElementState() in MouseEnter and MouseLeave event handlers.
You can expose various properties using DPs for customization.
NewShape.cs
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media.Effects;
namespace WpfStackOverflow.NewShape
{
public class CNewShape : Shape
{
public CNewShape()
{
// setting the defaults
this.Width = 40;
this.Height = 40;
this.Stroke = new SolidColorBrush() { Color = Colors.Red };
this.StrokeThickness = 5;
this.Effect = new DropShadowEffect() {
Color = Colors.Transparent,
BlurRadius = 1,
Direction = -150,
ShadowDepth = 1
};
// constructing the VisualStates
_constructVisualStates();
// event handlers
this.MouseEnter += CNewShape_MouseEnter;
this.MouseLeave += CNewShape_MouseLeave;
}
#region EventHandlers
void CNewShape_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSNormal", false);
}
void CNewShape_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSMouseEnter", false);
}
#endregion
#region Overrides
// This needs to be implemented as it is abstract in base class
GeometryGroup geo = new GeometryGroup();
protected override Geometry DefiningGeometry
{
get { return geo; }
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(this.Stroke, StrokeThickness);
drawingContext.DrawEllipse(Brushes.Transparent, pen, new Point(Width/2, Height/2), 40, 40);
drawingContext.DrawEllipse(Stroke, null, new Point(Width / 2, Height / 2), 30, 30);
base.OnRender(drawingContext);
}
#endregion
#region Helpers
private void _constructVisualStates()
{
VisualStateGroup vsg1 = new VisualStateGroup();
#region VSNormal (Normal Visual State)
VisualState stateVSNormal = new VisualState() { Name = "VSNormal" };
Storyboard sbVSNormal = new Storyboard();
ObjectAnimationUsingKeyFrames oa = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(oa, new PropertyPath("Effect"));
DiscreteObjectKeyFrame dokf = new DiscreteObjectKeyFrame(null);
oa.KeyFrames.Add(dokf);
sbVSNormal.Children.Add(oa);
stateVSNormal.Storyboard = sbVSNormal;
vsg1.States.Add(stateVSNormal);
#endregion
#region VSMouseEnter (MouseEnter Visual State)
VisualState stateVSMouseEnter = new VisualState() { Name = "VSMouseEnter" };
Storyboard sbVSMouseEnter = new Storyboard();
ColorAnimation caStrokeColor = new ColorAnimation();
caStrokeColor.To = (Color)ColorConverter.ConvertFromString("#FF24BCDE");
Storyboard.SetTargetProperty(caStrokeColor, new PropertyPath("(Shape.Stroke).(SolidColorBrush.Color)"));
sbVSMouseEnter.Children.Add(caStrokeColor);
ColorAnimation caEffectColor = new ColorAnimation();
caEffectColor.To = (Color)ColorConverter.ConvertFromString("#FFA4E1F3");
Storyboard.SetTargetProperty(caEffectColor, new PropertyPath("(Shape.Effect).(Color)"));
sbVSMouseEnter.Children.Add(caEffectColor);
DoubleAnimation daBlurRadius = new DoubleAnimation();
daBlurRadius.To = 10;
Storyboard.SetTargetProperty(daBlurRadius, new PropertyPath("(Shape.Effect).(BlurRadius)"));
sbVSMouseEnter.Children.Add(daBlurRadius);
DoubleAnimation daDirection = new DoubleAnimation();
daDirection.To = -190;
Storyboard.SetTargetProperty(daDirection, new PropertyPath("(Shape.Effect).(Direction)"));
sbVSMouseEnter.Children.Add(daDirection);
stateVSMouseEnter.Storyboard = sbVSMouseEnter;
vsg1.States.Add(stateVSMouseEnter);
#endregion
VisualStateManager.GetVisualStateGroups(this).Add(vsg1);
}
#endregion
}
}
Usage
<local:CNewShape Canvas.Left="70" Canvas.Top="52" Stroke="#FF374095" StrokeThickness="10" Width="100" Height="100" />
Output
Quality of the image is bad. On screen actual output looks good.
Whatever your trigger is that your control enters the Highlighted state, in that trigger just set the Effect property. For my test the "trigger" is a property:
public static readonly DependencyProperty ShowShadowProperty =
DependencyProperty.Register ("ShowShadow", typeof (bool), typeof (TestShape), new PropertyMetadata (false, ShowShadowChanged));
public bool ShowShadow
{
get { return (bool)GetValue (ShowShadowProperty); }
set { SetValue (ShowShadowProperty, value); }
}
private static void ShowShadowChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestShape)d).OnShowShadow ();
}
private void OnShowShadow ()
{
if (ShowShadow)
{
Effect = new DropShadowEffect { Direction = 0, ShadowDepth = 20, BlurRadius = 33, Opacity = 1, Color = Colors.Black};
}
else
{
Effect = null;
}
}
Which means you don't need to do anything in OnRender.

wpf, how can i get template element on custom-controls, after new instance immediately

questions:
1、i wanna get template elements when calling the constructor, but return null, any way?
2、i found it, get not null obj after loaded event, but i don't want this way.
snippet code:(to see my comments)
using System;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Windows.Input;
namespace WpfPropertyGrid_Demo
{
public class MyButton : Control
{
public static readonly DependencyProperty IsMouseDownProperty;
static MyButton()
{
IsMouseDownProperty = DependencyProperty.Register(
"IsMouseDown",
typeof(bool),
typeof(MyButton),
new FrameworkPropertyMetadata(false)
);
}
public bool IsMouseDown
{
get { return (bool)GetValue(IsMouseDownProperty); }
set { SetValue(IsMouseDownProperty, value); }
}
private BitmapImage _normalImg;
private BitmapImage _overImg;
private BitmapImage _clickImg;
private BitmapImage _disabledImg;
public MyButton()
{
_normalImg = new BitmapImage(new Uri("../../Images/ScrollerBtnBg.png", UriKind.RelativeOrAbsolute));
_overImg = new BitmapImage(new Uri("../../Images/ScrollerThumbnailBtnBg.png", UriKind.RelativeOrAbsolute));
_clickImg = new BitmapImage(new Uri("../../Images/ScrollerThumbnailBg.png", UriKind.RelativeOrAbsolute));
_disabledImg = _clickImg;
var style = new Style(typeof(MyButton));
var controlTemplate = new ControlTemplate();
var gridFactory = new FrameworkElementFactory(typeof(Grid));
var imgFacotry = new FrameworkElementFactory(typeof(Image));
imgFacotry.Name = "Image";
imgFacotry.SetValue(Image.SourceProperty, _normalImg);
gridFactory.AppendChild(imgFacotry);
controlTemplate.VisualTree = gridFactory;
var overTrigger = new Trigger();
overTrigger.Property = UIElement.IsMouseOverProperty;
overTrigger.Value = true;
overTrigger.Setters.Add(new Setter(Image.SourceProperty, _overImg, "Image"));
var disabledTriiger = new Trigger();
disabledTriiger.Property = UIElement.IsEnabledProperty;
disabledTriiger.Value = false;
disabledTriiger.Setters.Add(new Setter(Image.SourceProperty, _disabledImg, "Image"));
var downTrigger = new Trigger();
downTrigger.Property = MyButton.IsMouseDownProperty;
downTrigger.Value = true;
downTrigger.Setters.Add(new Setter(Image.SourceProperty, _clickImg, "Image"));
controlTemplate.Triggers.Add(overTrigger);
controlTemplate.Triggers.Add(disabledTriiger);
controlTemplate.Triggers.Add(downTrigger);
style.Setters.Add(new Setter(Control.TemplateProperty, controlTemplate));
this.PreviewMouseDown += new System.Windows.Input.MouseButtonEventHandler(MyButton_PreviewMouseDown);
this.PreviewMouseUp += new System.Windows.Input.MouseButtonEventHandler(MyButton_PreviewMouseUp);
this.MouseLeave += new System.Windows.Input.MouseEventHandler(MyButton_MouseLeave);
this.MouseEnter += new MouseEventHandler(MyButton_MouseEnter);
Style = style;
// 1、why cann't find "Image" element, it return null obj, i wanna get it immediately, any way?
// 2、return not null after loaded event
// var image = controlTemplate.FindName("Image", this );
this.Loaded += new RoutedEventHandler(MyButton_Loaded);
}
void MyButton_MouseEnter(object sender, MouseEventArgs e)
{
IsMouseDown = e.LeftButton == MouseButtonState.Pressed;
}
void MyButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
IsMouseDown = false;
}
void MyButton_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
IsMouseDown = false;
}
void MyButton_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
IsMouseDown = true;
}
void MyButton_Loaded(object sender, RoutedEventArgs e)
{
// return not null after loaded event
var image = this.Template.FindName("Image", this) as Image;
}
}
}
Try Visual Helper Class here I hope this will help

refresh datagrid items without losing selected row - XAML

I have two datagrid's ( Master/detaiL). I refresh my window using DispatcherTimer for every 5 secs. when I select any row on my Master grid, the focus on the selected row stays only for those 5 secs, after that the selection focus moves to the top most row.
how to overcome this issue in XAML?
EDIT
public partial class MyWindow : Window
{
ProdEntities _prodEntities = new ProdEntities();
public MyWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
RebindData();
SetTimer();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void SetTimer()
{
DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 5);
dispatcherTimer.Start();
}
protected void dispatcherTimer_Tick(object sender, EventArgs e)
{
RebindData();
}
private void RebindData()
{
CollectionViewSource serversViewSource = (CollectionViewSource)(FindResource("serversViewSource"));
IQueryable<Server> serversQuery = this.GetServersQuery(_prodEntities );
serversViewSource.Source = serversQuery.ToList();
//serversViewSource.View.Refresh();
}
private IQueryable<Server> GetServersQuery(ProdEntities _prodEntities)
{
var serversQuery = _prodEntities.Servers.Where(c => c.Components.Any());
return serversQuery;
}
private void SaveChanges_Click(object sender, RoutedEventArgs e)
{
_prodEntities.SaveChanges();
}
}
XAML
<DataGrid AutoGenerateColumns="False" Foreground="DarkBlue" DataContext="StaticResource serversComponentsViewSource}"
ItemsSource="{Binding}" Name="componentsDataGrid">
Hi you need to preserve SelectedItem of your DataGrid before you call
serversViewSource.Source = serversQuery.ToList();
and then assign it back to SelectedItem of that DataGrid. But you will have to find the item in Collection that has same Values as that in preserved SelectedItem before you assign that object back to SelectedItem.
private void RebindData()
{
CollectionViewSource serversViewSource = (CollectionViewSource)(FindResource("serversViewSource"));
var selectedItem = (Server)componentsDataGrid.SelectedItem;
IQueryable<Server> serversQuery = this.GetServersQuery(_prodEntities);
serversViewSource.Source = serversQuery.ToList();
foreach (var item in (IEnumerable<Server>)serversViewSource.Source )
{
if (selectedItem.PropName == item.PropName)//Compare the values here
{
componentsDataGrid.SelectedItem = item;
break;
}
}
//serversViewSource.View.Refresh();
}

GridLength animation using keyframes?

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
Any advice?
Create an attached behavior and animate it instead.
Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:
public class AnimatableProperties
{
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached("Width",
typeof(double),
typeof(DependencyObject),
new PropertyMetadata(-1, (o, e) =>
{
AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
}));
public static void SetWidth(DependencyObject o,
double e)
{
o.SetValue(AnimatableProperties.WidthProperty, e);
}
public static double GetWidth(DependencyObject o)
{
return (double)o.GetValue(AnimatableProperties.WidthProperty);
}
private static void OnWidthChanged(DependencyObject target,
double e)
{
target.SetValue(Grid.WidthProperty, new GridLength(e));
}
}
That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.
P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.
It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:
public class ColumnDefinitionDoubleAnimationAdapter : Control
{
#region Dependency Properties
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));
private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
}
#endregion
#region Fields
private ColumnDefinition _ColumnDefinition;
#endregion
#region Constructor
public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
{
_ColumnDefinition = columnDefinition;
}
#endregion
#region Public Properties
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
_ColumnDefinition.Width = new GridLength(value);
}
}
#endregion
}
Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.
Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:
public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.EasingFunction = ease;
animation.Duration = new Duration(duration);
storyboard.Children.Add(animation);
animation.From = from;
animation.To = to;
var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);
Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));
storyboard.Completed += (a, b) =>
{
taskCompletionSource.SetResult(true);
};
storyboard.Begin();
await taskCompletionSource.Task;
}
And an example usage:
private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
{
TheMenu.IsOpen = !TheMenu.IsOpen;
var twoSeconds = TimeSpan.FromMilliseconds(120);
var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
if (TheMenu.IsOpen)
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
}
else
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
}
}

WPF: Color under the pointer

I have a control with a gradiant background. On the MouseDown or MouseUp event I want to capture what color the pixel is immeidately under the mouse pointer. How would I do that?
I created a behavior that can be attached to an Image object to grab the color, which is 100% WPF. Here is the behavior; it could be tweaked to work with any "Visual", not just an Image.
[Note: I hardcoded 96dpi for the creation of RenderTargetBitmap... your milage may vary]
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Company.Solution.Project.Utilities.Behaviors
{
[Description("Used to sample the color under mouse for the image when the mouse is pressed. ")]
public class ImageBehaviorMouseDownPointSampleToColor : Behavior<Image>
{
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Color),
typeof(ImageBehaviorMouseDownPointSampleToColor),
new UIPropertyMetadata(Colors.White));
public Color SelectedColor
{
get { return (Color)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseDown += AssociatedObject_MouseDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
}
private void AssociatedObject_MouseDown(object sender, MouseButtonEventArgs e)
{
SamplePixelForColor();
}
private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
SamplePixelForColor();
}
}
private void SamplePixelForColor()
{
// Retrieve the coordinate of the mouse position in relation to the supplied image.
Point point = Mouse.GetPosition(AssociatedObject);
// Use RenderTargetBitmap to get the visual, in case the image has been transformed.
var renderTargetBitmap = new RenderTargetBitmap((int)AssociatedObject.ActualWidth,
(int)AssociatedObject.ActualHeight,
96, 96, PixelFormats.Default);
renderTargetBitmap.Render(AssociatedObject);
// Make sure that the point is within the dimensions of the image.
if ((point.X <= renderTargetBitmap.PixelWidth) && (point.Y <= renderTargetBitmap.PixelHeight))
{
// Create a cropped image at the supplied point coordinates.
var croppedBitmap = new CroppedBitmap(renderTargetBitmap,
new Int32Rect((int)point.X, (int)point.Y, 1, 1));
// Copy the sampled pixel to a byte array.
var pixels = new byte[4];
croppedBitmap.CopyPixels(pixels, 4, 0);
// Assign the sampled color to a SolidColorBrush and return as conversion.
SelectedColor = Color.FromArgb(255, pixels[2], pixels[1], pixels[0]);
}
}
}
}

Resources