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
Related
I want to draw line on run time using mouse.I have tried in below way
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement f = e.Source as FrameworkElement;
FrameworkElement pr = f.Parent as FrameworkElement;
Rect feRect = f.TransformToAncestor(pr).TransformBounds(
new Rect(0.0, 0.0, f.ActualWidth, f.ActualHeight));
Image image = sender as Image;
Point textLocation = e.GetPosition(image);
textLocation.Offset(-4, -4);
var Top = feRect.Height - textLocation.Y;
var Bottom = textLocation.Y - 1;
var Left = textLocation.X - 1;
var Right = feRect.Width-textLocation.X;
// Create an annotation where the mouse cursor is located.and add control using adorner layer
_currentAnnotations = ImageAnnotation.Create(
image,
textLocation,
feRect
);
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Image image = sender as Image;
EndPosition = e.MouseDevice.GetPosition(image);
// Draw next line and...
l.X1 = StartPosition.X;
l.X2 = EndPosition.X;
l.Y1 = StartPosition.Y;
l.Y2 = EndPosition.Y;
l.Stroke = Brushes.Red;
l.StrokeThickness = 5;
StartPosition = EndPosition;
}
if(_currentAnnotations!=null && l!=null)
_currentAnnotations.Lines = l;
}
But it doesn't give the result as expected. the line that am getting is different from the mouse location. my output should be like a pen tool. 1.what's wrong with my way?
2. Is inkCanvas the only way to draw line in wpf?if yes why so?
3.
1.what's wrong with my way?
You could try to set the StartPosition property in the Image_MouseLeftButtonDown event handler:
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement f = e.Source as FrameworkElement;
FrameworkElement pr = f.Parent as FrameworkElement;
Rect feRect = f.TransformToAncestor(pr).TransformBounds(
new Rect(0.0, 0.0, f.ActualWidth, f.ActualHeight));
Image image = sender as Image;
...
StartPosition = e.MouseDevice.GetPosition(image);
}
InkCanvas is the only built-in WPF control that receives and displays ink strokes. But you could for example add lines to a Canvas or perform any other kind of custom drawing action yourself.
Here is a basic sample that should give you the idea.
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent();
}
Point EndPosition;
Point StartPosition;
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
FrameworkElement fe = sender as FrameworkElement;
EndPosition = e.MouseDevice.GetPosition(fe);
Line l = new Line();
l.X1 = StartPosition.X;
l.X2 = EndPosition.X;
l.Y1 = StartPosition.Y;
l.Y2 = EndPosition.Y;
l.Stroke = Brushes.Red;
l.StrokeThickness = 5;
StartPosition = EndPosition;
canvas.Children.Add(l);
}
}
private void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
StartPosition = e.MouseDevice.GetPosition(fe);
}
}
<Canvas x:Name="canvas" Width="500" Height="500" Background="Yellow"
MouseLeftButtonDown="canvas_MouseLeftButtonDown" MouseMove="canvas_MouseMove" />
I am working on a project for my Uni, and I am currently stuck on a Pixel Perfect Collision Detection from this website http://www.andybeaulieu.com/Home/tabid/67/EntryID/160/Default.aspx. I have downloded example project that is using this collsion detection and it is working fine even with my own pictures. I have done the same thing in my project and it is not working. Here is the link to my app: https://www.cubby.com/pl/LostInTheMath.zip/_dd23e2c827604c068a3fe63ff42d22b2 could anyone tell me whats wrong with it? Thank you.
Here is the main code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
namespace LostInTheMath
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
private void UserCntrl_MouseMove(object sender, MouseEventArgs e)
{
Point pt = e.GetPosition(cnvHitTest);
Monkey.SetValue(Canvas.LeftProperty, pt.X);
Monkey.SetValue(Canvas.TopProperty, pt.Y);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
Image MonkeyShell = Monkey.FindName("imgMonkey") as Image;
Image Mazer = Maze.FindName("imgMaze") as Image;
if (CheckCollision(Monkey, MonkeyShell, Maze, Mazer))
{
Monkey.Width = 1000;
txtStatus.Text = "Collision with XAML Element!";
return;
}
txtStatus.Text = string.Empty;
}
private bool CheckCollision(FrameworkElement control1, FrameworkElement controlElem1, FrameworkElement control2, FrameworkElement controlElem2)
{
// first see if sprite rectangles collide
Rect rect1 = UserControlBounds(control1);
Rect rect2 = UserControlBounds(control2);
rect1.Intersect(rect2);
if (rect1 == Rect.Empty)
{
// no collision - GET OUT!
return false;
}
else
{
bool bCollision = false;
Point ptCheck = new Point();
// NOTE that creating the writeablebitmap is a bit intense
// so we will do this once and store results in Tag property
// in a real game, you might abstract this into a Sprite class.
if (controlElem1 is Image)
controlElem1.Tag = GetWriteableBitmap(control1);
if (controlElem2 is Image)
controlElem2.Tag = GetWriteableBitmap(control2);
// now we do a more accurate pixel hit test
for (int x = Convert.ToInt32(rect1.X); x < Convert.ToInt32(rect1.X + rect1.Width); x++)
{
for (int y = Convert.ToInt32(rect1.Y); y < Convert.ToInt32(rect1.Y + rect1.Height); y++)
{
ptCheck.X = x;
ptCheck.Y = y;
if (CheckCollisionPoint(ptCheck, control1, controlElem1))
if (CheckCollisionPoint(ptCheck, control2, controlElem2))
{
bCollision = true;
break;
}
}
if (bCollision) break;
}
return bCollision;
}
}
public bool CheckCollisionPoint(Point pt, FrameworkElement control, FrameworkElement controlElem)
{
if (controlElem is Image)
{
// NOTE that we saved the WB in the Tag object for performance.
// in a real app, you might abstract this in your sprite class.
WriteableBitmap wb = controlElem.Tag as WriteableBitmap;
int width = wb.PixelWidth;
int height = wb.PixelHeight;
double offSetX = Convert.ToDouble(control.GetValue(Canvas.LeftProperty));
double offSetY = Convert.ToDouble(control.GetValue(Canvas.TopProperty));
pt.X = pt.X - offSetX;
pt.Y = pt.Y - offSetY;
int offset = Convert.ToInt32((width * pt.Y) + pt.X);
return (wb.Pixels[offset] != 0);
}
else
{
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(pt, controlElem) as List<UIElement>;
return (hits.Contains(controlElem));
}
}
private WriteableBitmap GetWriteableBitmap(FrameworkElement control)
{
WriteableBitmap wb = new WriteableBitmap((int)control.Width, (int)control.Height); ;
wb.Render(control, new TranslateTransform());
wb.Invalidate();
return wb;
}
public Rect UserControlBounds(FrameworkElement control)
{
Point ptTopLeft = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)), Convert.ToDouble(control.GetValue(Canvas.TopProperty)));
Point ptBottomRight = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)) + control.Width, Convert.ToDouble(control.GetValue(Canvas.TopProperty)) + control.Height);
return new Rect(ptTopLeft, ptBottomRight);
}
}
}
There is an very old WPF application of Hyper Tree - http://blogs.msdn.com/b/llobo/archive/2007/10/31/mindmap-app-using-hyperbolic-tree.aspx.
The source code can be found at codeplax.com -
http://hypertree.codeplex.com/releases/view/11524
I wanted to use this tree control in my silverlight application. Now the issue is that i am new to silverlight, and the code is using some WPF specific things.
Please suggest me to solve my problem.
Thanks in advance.
Abhinav
Update:
things like
FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions, InvalidateVisual(), OnRender override, child UIElements.
Code Added:
public class SmartBorder : Decorator
{
#region Dependency Properties
public static readonly DependencyProperty GlowBrushProperty =
DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(SmartBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
......
#region Dependency Property backing CLR properties
......
#endregion
// if the button is pressed, this fires
private static void OnRenderIsPressedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
if ((bool)e.NewValue == true)
{
border.BorderBrush = Brushes.Transparent;
border.BorderWidth = 2;
}
else
{
border.BorderBrush = Brushes.Red;
border.BorderWidth = 2;
}
border.InvalidateVisual();
}
}
// if the mouse is over the control, this fires
private static void OnRenderIsMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
border.InvalidateVisual();
}
}
// a series of methods which all make getting the default or currently selected brush easier
protected override void OnRender(DrawingContext dc)
{
Rect rc = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
LinearGradientBrush gradientOverlay = GetGradientOverlay();
Brush glowBrush = GetGlowBrush();
Brush backBrush = GetBackgroundBrush();
Brush borderBrush = GetBorderBrush();
Pen borderPen = new Pen(borderBrush, BorderWidth);
double cornerRadiusCache = CornerRadius;
// draw the highlight as necessary
if (RenderIsMouseOver)
{
Rect rcGlow = rc;
double glowMove = BorderWidth * 2;
rcGlow.Inflate(glowMove, glowMove);
glowMove = 0;
rcGlow.Offset(new Vector(glowMove, glowMove));
dc.DrawRoundedRectangle(GetOuterGlowBrush(), null, rcGlow, cornerRadiusCache, cornerRadiusCache);
}
// we want to clip anything that might errantly draw outside of the smart border control
dc.PushClip(new RectangleGeometry(rc, cornerRadiusCache, cornerRadiusCache));
dc.DrawRoundedRectangle(backBrush, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.DrawRoundedRectangle(gradientOverlay, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
if (!RenderIsPressed)
{
double clipBorderSize = BorderWidth * -4.0;
Rect rcClip = rc;
rcClip.Offset(clipBorderSize, clipBorderSize);
rcClip.Inflate(-clipBorderSize, -clipBorderSize);
dc.PushClip(new RectangleGeometry(rcClip, cornerRadiusCache, cornerRadiusCache));
dc.DrawEllipse(glowBrush, null, new Point(this.ActualWidth / 2, this.ActualHeight * 0.10), this.ActualWidth * 0.80, this.ActualHeight * 0.40);
dc.Pop();
}
// just draw the border now to make sure it overlaps everything nicely
dc.DrawRoundedRectangle(null, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.Pop();
//base.OnRender(drawingContext);
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = this.Child as UIElement;
double borderThickness = BorderWidth * 2.0;
if (child != null)
{
...
}
return new Size(Math.Min(borderThickness, constraint.Width), Math.Min(borderThickness, constraint.Height));
}
}
Regarding FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions and value coercions etc. for Silverlight see the WPF_Compatibility solution under the ClipFlair codebase (http://clipflair.codeplex.com)
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]);
}
}
}
}
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;
}