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]);
}
}
}
}
Related
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.
I'm learning MVVM design pattern so I'm trying to change some operation into Command.
Here's an example, MainWindow has a Canvas as the container, and the user can draw rectangle through dragging. So I write the code as below
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
StartPoint = e.GetPosition(this);
shape = new Rectangle();
shape.Fill = Brushes.Transparent;
shape.Stroke = Brushes.Black;
shape.StrokeThickness = 1;
this.Children.Add(shape);
}
protected override void OnMouseMove(MouseButtonEventArgs e)
{
Point endpoint = e.GetPosition(this);
double left = Math.Min(endpoint.X, StartPoint.X);
double top = Math.Min(endpoint.Y, StartPoint.Y);
shape.Margin = new Thickness(left, top, 0, 0);
shape.Width = Math.Abs(endpoint.X - StartPoint.X);
shape.Height = Math.Abs(endpoint.Y - StartPoint.Y);
shape.Stroke = Brushes.Black;
shape.StrokeThickness = 2;
}
protected override void OnMouseLeave(MouseButtonEventArgs e)
{
//end
}
Since maybe I wanna add Undo function so that the rectangle will disappear after Undo invoked, so I want to make these 3 steps into one command. How should I do this? Thanks.
Microsoft's Expression Blend Behaviors does this. To implement and use your own behaviors you do not need Expression Blend, just the SDK which is available for download.
The way it works is you implement Behavior where T : DependencyObject. The class has two overrideable methods OnAttach() and OnDetach() which you wire and unwire to your events, and put the above logic inside the behavior. If you were to name your class DrawRectangleBehavior, then, all you need to do is this:
....
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
....
<Canvas>
<i:Interaction.Behaviors>
<myBlend:DrawRectangleBehavior />
</i:Interaction.Behaviors>
</Canvas>
And the behavior (I did not test this)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MyCompany.Common.Behaviors
{
public class DrawRectangleBehavior : Behavior<Canvas>
{
Point StartPoint;
Shape shape;
protected override void OnAttached()
{
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
AssociatedObject.PreviewMouseMove += OnMouseMove;
AssociatedObject.MouseLeave += OnMouseLeave;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
AssociatedObject.PreviewMouseMove -= OnMouseMove;
AssociatedObject.MouseLeave -= OnMouseLeave;
}
protected void OnMouseLeftButtonDown(object o, MouseButtonEventArgs e)
{
StartPoint = e.GetPosition(AssociatedObject);
shape = new Rectangle();
shape.Fill = Brushes.Transparent;
shape.Stroke = Brushes.Black;
shape.StrokeThickness = 1;
AssociatedObject.Children.Add(shape);
}
protected void OnMouseMove(object o, MouseEventArgs e)
{
Point endpoint = e.GetPosition(AssociatedObject);
double left = Math.Min(endpoint.X, StartPoint.X);
double top = Math.Min(endpoint.Y, StartPoint.Y);
shape.Margin = new Thickness(left, top, 0, 0);
shape.Width = Math.Abs(endpoint.X - StartPoint.X);
shape.Height = Math.Abs(endpoint.Y - StartPoint.Y);
shape.Stroke = Brushes.Black;
shape.StrokeThickness = 2;
}
protected void OnMouseLeave(object o, MouseEventArgs e)
{
//end
}
}
}
And you have a reusable piece of code.
Please see the following tutorial WPF Tutorial | Blend Behaviors
And the following download link Expression Blend SDK
I am using a custom style in a resource dictionary for the LineDataPoint available with the toolkit.
I want the datapoint to increase in size on the mouseover event and the revert back to its original size once the mouse leaves. What's the best way to implement this?
I haven't found a better solution then using an extended class with necessary functionality:
public class ExtendedLineSeries : LineSeries
{
//I assume that all points have the same size
private double _originalDataPointWidth;
private double _originalDataPointHeight;
protected override DataPoint CreateDataPoint()
{
var dp = base.CreateDataPoint();
if (this.IncreaseDataPointSizeTo != null)
{
dp.MouseEnter += new MouseEventHandler(OnDataPointMouseEnter);
dp.MouseLeave += new MouseEventHandler(OnDataPointMouseLeave);
}
return dp;
}
/// <summary>
/// The width and height to which the point is increased in size
/// </summary>
public double? IncreaseDataPointSizeTo { get; set; }
void OnDataPointMouseLeave(object sender, MouseEventArgs e)
{
var dp = sender as DataPoint;
if (dp != null)
{
//return to the original size
dp.Width = _originalDataPointWidth;
dp.Height = _originalDataPointHeight;
dp.UpdateLayout();
base.UpdateDataPoint(dp);
}
}
void OnDataPointMouseEnter(object sender, MouseEventArgs e)
{
var dp = sender as DataPoint;
if (dp != null)
{
//remember the original size and enlarge the data point
_originalDataPointWidth = dp.ActualWidth;
_originalDataPointHeight = dp.ActualHeight;
dp.Width = dp.Height = IncreaseDataPointSizeTo.Value;
dp.UpdateLayout();
base.UpdateDataPoint(dp);
}
}
}
This class can be used everywhere where you use the common LineSeries class. It has the additional property IncreaseDataPointSizeTo which contains the final size in width and height of the hovered datapoint.
Example of xaml code:
<charting:Chart>
<charting:Chart.Series>
<ext:ExtendedLineSeries IsSelectionEnabled="True"
IncreaseDataPointSizeTo="16" ...
I found Customize a panel with Autoscroll property at http://www.codeproject.com/KB/miscctrl/CustomAutoScrollPanel.aspx that is wrapper around a Panel with AutoScroll = True.
I like this control because it provides the "performScrollHorizontal" and "performScrollVertical" methods. Yet, it uses the Windows API functions instead of ScrollableControl and its VerticalScroll and HorizontalScroll properties.
Is this a good control to use? I think it should be using ScrollableControl instead of the Windows API. What do you think? Is there a better control available? Do I even need a control? I would think that ScrollableControl provides everything I would need.
EDIT: I found the HScrollBar and VScrollBar controls. Should I be using them?
These two other controls are nice but do not give me a way to control the scroll bars like the above control does.
A scrollable, zoomable, and scalable picture box at
http://www.codeproject.com/KB/miscctrl/ScalablePictureBox.aspx
Pan and Zoom Very Large Images at
http://www.codeproject.com/KB/GDI-plus/PanZoomExample.aspx
What I really want is a control:
that scrolls when the user moves the mouse toward the edge of the control,
allows the user to pan
allows the user to zoom
supports using the mouse with Shift or Ctrl or Alt keys pressed
Any recommendations, help, or areas to look at is greatly appreciated. A control would be nice as I am not that good yet.
Some code to play with. It supports focus, panning and scrolling. Zooming is work-to-do, my laptop's mousepad got in the way of testing it. Use a timer to implement auto-scrolling at the edges.
using System;
using System.Drawing;
using System.Windows.Forms;
class ZoomPanel : Panel {
public ZoomPanel() {
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.Selectable, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.AutoScroll = this.TabStop = true;
}
public Image Image {
get { return mImage; }
set {
mImage = value;
Invalidate();
mZoom = 1.0;
this.AutoScrollMinSize = (mImage != null) ? mImage.Size : Size.Empty;
}
}
protected override void OnMouseDown(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
this.Cursor = Cursors.SizeAll;
mLastPos = e.Location;
this.Focus();
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) this.Cursor = Cursors.Default;
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
this.AutoScrollPosition = new Point(
-this.AutoScrollPosition.X - e.X + mLastPos.X,
-this.AutoScrollPosition.Y - e.Y + mLastPos.Y);
mLastPos = e.Location;
Invalidate();
}
base.OnMouseMove(e);
}
protected override void OnMouseWheel(MouseEventArgs e) {
if (mImage != null) {
mZoom *= 1.0 + 0.3 * e.Delta / 120;
this.AutoScrollMinSize = new Size((int)(mZoom * mImage.Width),
(int)(mZoom * mImage.Height)); \
// TODO: calculate new AutoScrollPosition...
Invalidate();
}
base.OnMouseWheel(e);
}
protected override void OnPaint(PaintEventArgs e) {
if (mImage != null) {
var state = e.Graphics.Save();
e.Graphics.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
e.Graphics.DrawImage(mImage,
new Rectangle(0, 0, this.AutoScrollMinSize.Width, this.AutoScrollMinSize.Height));
e.Graphics.Restore(state);
}
//if (this.Focused) ControlPaint.DrawFocusRectangle(e.Graphics,
// new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height));
base.OnPaint(e);
}
protected override void OnEnter(EventArgs e) { Invalidate(); base.OnEnter(e); }
protected override void OnLeave(EventArgs e) { Invalidate(); base.OnLeave(e); }
private double mZoom = 1.0;
private Point mLastPos;
private Image mImage;
}
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.