How let WPF UI slide animation more physical? - c#

If we want to slide the screen, we need some events.
Like Mouse Move Event, Mouse Down Event, Mouse UP event
But the problem is we only can do some animation only when the finger touch the screen, I mean: when It touch the screen move, it will move, when we release it, it will stop(or give it a position let it stop to there). But I want some animation like IPhone main screen do. If our finger slide more fast, the animation more fast (or the animation slide to more far place).
Just like now we use the Photoshop, when the picture zoom out to very large, when we move the hand more fast, it will move to very far.
Another example is it will slow down very slowly, not immediately.
Also it will know my finger sliding fast , or slow....then it will slide slow or fast...

I use a modified version of the the code found here in my own programs. Usage is simple as it is an attached behavior and can be applied to a style so that all of your scroll viewers automatically behave in this way. It works by using the same (tunneled) events you mention (OnPreviewMouseDown, OnPreviewMouseUp, and OnPreviewMouseMove). During the handling of OnPreviewMouseMove, inertia is calculated and the scrollbar is moved in a simulated physical manner. There is also a friction property which can be set to change the length of time a scrollbar "glides".

This is a quick example of a control I did for a test with inertia, using a ScrollViewer.
Hope this helps.
public partial class HomeFeed : BaseControl
{
public HomeFeed()
{
InitializeComponent();
}
private bool IsDragging
{
get { return _isDragging; }
set
{
var start = _isDragging && !value;
_isDragging = value;
if (start)
{
new Thread(x =>
{
var c = 0;
while (ApplyVelocity())
{
c++;
Thread.Sleep(15);
}
}).Start();
}
}
}
private Point _mousePosition;
private Velocity _velocity = new Velocity();
private bool _isDragging;
private void HomeScrollViewer_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IsDragging = true;
_velocity.Reset();
_mousePosition = e.GetPosition(this);
e.Handled = true;
}
private void HomeScrollViewer_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging) return;
var pos = e.GetPosition(this);
var y = pos.Y - _mousePosition.Y;
if (y == 0)
{
return;
}
_velocity.TryUpdate(y);
HomeScrollViewer.ScrollToVerticalOffset(HomeScrollViewer.VerticalOffset - y);
_mousePosition = pos;
}
private void HomeScrollViewer_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging) return;
IsDragging = false;
e.Handled = true;
}
private void HomeScrollViewer_OnMouseLeave(object sender, MouseEventArgs e)
{
if (!IsDragging) return;
IsDragging = false;
e.Handled = true;
}
private bool ApplyVelocity()
{
if (IsDragging || _velocity.Value == 0)
{
return false;
}
Dispatcher.BeginInvoke(new Action(() => HomeScrollViewer.ScrollToVerticalOffset(HomeScrollViewer.VerticalOffset - _velocity.Value)));
var size = Math.Abs(_velocity.Value);
var sign = size / _velocity.Value;
_velocity.Value = sign * Math.Max(0, Math.Min(size*0.95, size - 1));
return true;
}
}
public class Velocity
{
private readonly int _timespan;
public double Value { get; set; }
public DateTime SetAt { get; set; }
public Velocity(int timespan = 1000)
{
_timespan = timespan;
Value = 0;
SetAt = DateTime.Now;
}
public void TryUpdate(double value)
{
if (value == 0)
{
return;
}
if (SetAt.Add(TimeSpan.FromMilliseconds(_timespan)) > DateTime.Now)
{
SetAt = DateTime.Now;
Value = value;
return;
}
if (value*Value < 0)
{
SetAt = DateTime.Now;
Value = value;
return;
}
if (Math.Abs(value) > Math.Abs(Value))
{
SetAt = DateTime.Now;
Value = value;
return;
}
}
public void Reset()
{
Value = 0;
SetAt = DateTime.Now;
}
}

Related

How to draw an animated rectangle?

How to draw a rectangle that could move to the edge of the PictureBox and when it reached the edge, it would unfold. And there could be several such rectangles
Here is the code of the class with the Draw function (Pike.cs):
using System.Drawing;
namespace course_project
{
internal class Pike : Fish
{
private Point coordinates;
private Size size = new Size(40, 40);
private Size speed;
public Pike(Point data, AquariumForm aquariumForm) : base(Color.Green, aquariumForm)
{
Data = data;
speed = new Size();
}
public Pike Next { get; set; }
public override void Draw(Graphics graphics)
{
graphics.FillEllipse(brush, coordinates.X, coordinates.Y, size.Width, size.Height);
}
public void UpdateLocation(Rectangle bounds)
{
if (!bounds.Contains(coordinates + speed))
{
if (coordinates.X + speed.Width < bounds.Left || coordinates.X + speed.Width > bounds.Right - size.Width)
{
speed.Width *= -1;
}
if (coordinates.Y + speed.Height < bounds.Top || coordinates.Y + speed.Height > bounds.Bottom - size.Height)
{
speed.Height *= -1;
}
}
coordinates += speed;
}
}
}
Linked List (PikeFlock.cs):
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
namespace course_project
{
internal class PikeFlock : IEnumerable<Pike>
{
Pike head;
Pike tail;
int count;
public void Add(Point data, AquariumForm aquariumForm)
{
Pike pike = new Pike(data, aquariumForm);
if (head == null)
head = pike;
else
tail.Next = pike;
tail = pike;
count++;
}
public bool Remove(Point data)
{
Pike current = head;
Pike previous = null;
while (current != null)
{
if (current.Data.Equals(data))
{
if (previous != null)
{
previous.Next = current.Next;
if (current.Next == null)
{
tail = previous;
}
}
else
{
head = head.Next;
if (head == null)
tail = null;
}
count--;
return true;
}
previous = current;
current = current.Next;
}
return false;
}
public int Count { get { return count; } }
public bool IsEmpty { get { return count == 0; } }
public void Clear()
{
head = null;
tail = null;
count = 0;
}
public bool Contains(Point data)
{
Pike current = head;
while (current != null)
{
if (current.Data.Equals(data))
return true;
current = current.Next;
}
return false;
}
public void AppendFirst(Point data, AquariumForm aquariumForm)
{
Pike pike = new Pike(data, aquariumForm)
{
Next = head
};
head = pike;
if (count == 0)
{
tail = head;
}
count++;
}
public IEnumerator<Pike> GetEnumerator()
{
Pike current = head;
while (current != null)
{
yield return current;
current = current.Next;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this).GetEnumerator();
}
}
}
AquariumForm.cs (The form itself):
using System;
using System.Drawing;
using System.Windows.Forms;
namespace course_project
{
public partial class AquariumForm : Form
{
readonly Aquarium aquarium;
public AquariumForm()
{
InitializeComponent();
DoubleBuffered = true;
aquarium = new Aquarium(ClientRectangle);
aquarium_timer.Interval = 100;
aquarium_timer.Tick += Timer_Tick;
aquarium_timer.Enabled = true;
}
private void Timer_Tick(object sender, EventArgs e)
{
foreach (Pike pike in aquarium.pikeFlock)
{
pike.UpdateLocation(ClientRectangle);
}
foreach (Carp carp in aquarium.carpFlock)
{
carp.UpdateLocation(ClientRectangle);
}
Invalidate();
}
private void Add_carp_button_Click(object sender, EventArgs e)
{
aquarium.carpFlock.Add(new Point(), this);
}
private void Add_pike_button_Click(object sender, EventArgs e)
{
aquarium.pikeFlock.Add(new Point(), this);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
aquarium.Paint(e.Graphics);
}
}
}
Aquarium.cs:
using System.Drawing;
namespace course_project
{
internal class Aquarium
{
readonly public PikeFlock pikeFlock;
readonly public CarpFlock carpFlock;
readonly Color waterColor;
public Aquarium(Rectangle clientRectangle)
{
waterColor = Color.LightSkyBlue;
pikeFlock = new PikeFlock();
carpFlock = new CarpFlock();
}
public void Paint(Graphics graphics)
{
graphics.Clear(waterColor);
foreach (Pike pike in pikeFlock)
pike.Draw(graphics);
foreach (Carp carp in carpFlock)
carp.Draw(graphics);
}
}
}
Fish.cs
using System.Drawing;
namespace course_project
{
internal class Fish
{
private protected Brush brush;
private protected AquariumForm aquarium_form;
public Fish(Color color, AquariumForm aquariumForm)
{
aquarium_form = aquariumForm;
brush = new SolidBrush(color);
}
public virtual void Draw(Graphics graphics) { }
public Point Data { get; set; }
}
}
Confused just at the point where you need to animate it all. By clicking the button everything is created as it should be, but animation does not work, even I do not know how :(
You didn't post fish.cs so without testing I would suggest the following changes:
Do not use PictureBox, it just complicates things if you perform custom animations this way. So instead of extracting the Graphics from its assigned Image property, which is backed by a Bitmap that has the same size as your form (btw. what happens if you resize your form?) simply just use the Graphics that is already there when your form is redrawn.
Unlike PictureBox, a Form does not use double buffering by default so you might want to set DoubleBuffered = true in the form constructor to avoid flickering.
Override OnPaint in the form and initiate the whole repaint session from there:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
aquarium.Paint(e.Graphics); // instead of your Init() method
}
It means you don't need bitmap, graphics and aquarium_form fields in your Aquarium class. Instead, you can pass the form's Graphics to a Paint method:
// in Aquarium.cs:
public void Paint(Graphics graphics)
{
// Please note that graphics can have any size now so it works even if you
// resize the form. Please also note that I treat pikeFlock as a Pike
// enumeration instead of just coordinates
g.Clear(waterColor);
foreach (Pike pike in pikeFlock) // instead of int[] items
pike.Draw(graphics); // instead of Draw(int[])
}
Your PikeFlock is just IEnumerable, I would change it to IEnumerable<Pike> to make it strongly typed.
It also means that PikeFlock.GetEnumerator should yield Pike instances instead of int arrays: yield return current instead of current.Data
Pike has now int[] as coordinates. I would change it to Point. And do not pass new random coordinates in every drawing iteration because every fish will just randomly 'teleport' here and there. Instead, maintain the current horizontal and vertical speed.
// in Pike.cs:
private Point coordinates;
private Size size = new Size(40, 40);
// speed is declared as Size because there is an operator overload for Point + Size
// Do not forget to initialize speed in constructor
private Size speed;
public override void Draw(Graphics graphics)
{
// no Refresh is needed because graphics comes from the form's OnPaint now
graphics.FillEllipse(brush, coordinates.X, coordinates.Y, size.Width, size.Height);
}
public void UpdateLocation(Rectangle bounds)
{
// if a fish would go out of bounds invert its vertical or horizontal speed
// TODO: if you shrink the form an excluded fish will never come back
// because its direction will oscillate until you enlarge the form again.
if (!bounds.Contains(coordinates + speed))
{
if (coordinates.X + speed.Width < bounds.Left
|| coordinates.X + speed.Width > bounds.Right - size.Width)
{
speed.Width *= -1;
}
if (coordinates.Y + speed.Height < bounds.Top
|| coordinates.Y + speed.Height > bounds.Bottom - size.Height)
{
speed.Height *= -1;
}
}
coordinates += speed;
}
Now the only thing is missing is the animation itself. You can add a Timer component to the form from the Toolbox.
// form:
public AquariumForm()
{
InitializeComponent();
DoubleBuffered = true;
// No need to pass the Form anymore. Pass the bounds instead to
// generate the initial coordinates within the correct range.
// No Random field is needed here, use one in Aquarium constructor only.
aquarium = new Aquarium(ClientRectangle);
timer.Interval = 100;
timer.Tick += Timer_Tick;
timer.Enabled = true;
}
private void Timer_Tick(object sender, EventArgs e)
{
// Updating coordinates of all fish. We just pass the current bounds.
foreach (Pike pike in aquarium.pikeFlock)
pike.UpdateLocation(ClientRectangle);
// Invalidating the form's graphics so a repaint will be automatically
// called after processing the pending events.
Invalidate();
}

how do I make beautiful flipping images?

When I opened the game launcher, I noticed how the news was implemented there.
And I really liked this idea, so I decided to do it in my project, first of all I made a panel, stuffed a few images into it, and then actually made two buttons with which I plan to flip through the images. BUT how to do it smoothly? Here is where the problem is, I do not understand how to make a smooth flipping of
the image
I can't tell how the images are sliding, flipping, stretching or whatever, but I think WinForms with GDI+ isn't the best choice. I think WPF would be better. I would also recommend using a suitable library for those kind of image manipulations.
However, if you want it very(!) simple you could use this class:
public class SlideAnimation
{
public event EventHandler<AnimationEventArgs> AnimationFinished;
private readonly Control Control;
private readonly Timer Timer;
private float fromXPosition;
public SlideAnimation(Control ctrl)
{
Control = ctrl;
Timer = new Timer();
Timer.Interval = 10;
Timer.Tick += Timer_Tick;
Control.Paint += Control_Paint;
}
public float Speed { get; set; }
public Image From { get; set; }
public Image To { get; set; }
public AnimationDirection Direction { get; set; }
public bool IsRunning
{
get
{
return Timer.Enabled;
}
}
public void StartAnimation()
{
// maybe move those checks into the setter of the corresponding property
if (this.From == null)
throw new InvalidOperationException();
if (this.To == null)
throw new InvalidOperationException();
if (this.Speed <= 0)
throw new InvalidOperationException();
fromXPosition = 0;
Timer.Enabled = true;
}
protected void OnAnimationFinished(AnimationEventArgs e)
{
AnimationFinished?.Invoke(this, e);
}
private void Timer_Tick(object sender, EventArgs e)
{
// increase or decrease the position of the first image
fromXPosition = fromXPosition + (this.Speed * this.Direction);
Control.Invalidate();
if (Math.Abs(fromXPosition) >= this.From.Width)
{
Timer.Enabled = false;
OnAnimationFinished(new AnimationEventArgs(this.Direction));
}
}
private void Control_Paint(object sender, PaintEventArgs e)
{
if (!Timer.Enabled)
return;
// draw both images next to each other depending on the direction
e.Graphics.DrawImage(this.From, new PointF(fromXPosition, 0));
e.Graphics.DrawImage(this.To, new PointF(fromXPosition - (this.From.Width * this.Direction), 0));
}
}
public enum AnimationDirection
{
Forward = -1,
Backward = 1
}
public class AnimationEventArgs : EventArgs
{
public AnimationEventArgs(AnimationDirection direction)
{
Direction = direction;
}
public AnimationDirection Direction { get; }
}
This class will only draw the images while the animation is active. Every other invalidation will not trigger the Control_Paint method.
Use following code for your Form:
public class Form1
{
private List<Image> imgList = new List<Image>();
private int currentIndex = 0;
private SlideAnimation animation;
public Slideshow()
{
InitializeComponent();
imgList.Add(Image.FromFile("pic1.bmp"));
imgList.Add(Image.FromFile("pic2.bmp"));
imgList.Add(Image.FromFile("pic3.bmp"));
imgList.Add(Image.FromFile("pic4.bmp"));
imgList.Add(Image.FromFile("pic5.bmp"));
animation = new SlideAnimation(this.Panel1);
animation.Speed = 20;
animation.AnimationFinished += AnimationFinished;
}
private void btnPrev_Click(object sender, EventArgs e)
{
if (currentIndex == 0)
return;
if (animation.IsRunning)
return;
animation.Direction = AnimationDirection.Backward;
animation.From = imgList[currentIndex];
animation.To = imgList[currentIndex - 1];
animation.StartAnimation();
}
private void btnNext_Click(object sender, EventArgs e)
{
if (currentIndex == imgList.Count - 1)
return;
if (animation.IsRunning)
return;
animation.Direction = AnimationDirection.Forward;
animation.From = imgList[currentIndex];
animation.To = imgList[currentIndex + 1];
animation.StartAnimation();
}
private void AnimationFinished(object sender, AnimationEventArgs e)
{
currentIndex = currentIndex - (1 * e.Direction);
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(imgList[currentIndex], 0, 0);
}
}
Since there are a lot of drawing operations you may use a panel which supports DoubleBuffer.
public class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
}
Keep in mind that this example is very simple and far from "fancy".

Capture user click and typing in a custom WPF TextBox-like control?

Trying to make an equation editor like that in Microsoft Word in C# and WPF. XML cannot be used; it has to be purely programmatic.
Right now I have LineGUIObject : System.Windows.Controls.WrapPanel, which is like System.Windows.Controls.TextBox, except that instead of just showing strings it shows each element of a List<System.Windows.UIElement> in order.
Now I want for a user to be able to click on an instance of LineGUIObject and type into it. The holdup is that I don't know how to capture the user's click or read the input that they type. How can this be done?
Note: This question is not asking how to handle input once captured; just how to get the input in the first place. For example, is there some event that fires off after the user clicks it or something? I can't seem to find one for System.Windows.Controls.WrapPanel, which might imply that I need to use another type of object, or..?
Current code:
public class LineGUIObject
: System.Windows.Controls.WrapPanel
{
private List<System.Windows.UIElement> _uiElementList;
private CursorGUIObject _cursor;
private int? _cursorIndex;
public LineGUIObject(System.Windows.Threading.Dispatcher dispatcher)
: base()
{
this.UIElementList = new List<System.Windows.UIElement>();
this.Cursor = new CursorGUIObject(dispatcher, 25, 1.5, 250);
this.UIElementList.Add(this.Cursor);
this.AddText("[junk string just to see this otherwise invisible object while debugging]");
}
protected void InterpretUserKeyStroke(/* ??? */)
{
//How do we get this method to be called on user input,
//e.g. when the user types "1"?
throw new NotImplementedException();
}
protected void AddText(string text)
{
this.UIElementList.Add(new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run(text)));
this.UpdateDisplay();
}
protected List<System.Windows.UIElement> UIElementList { get { return this._uiElementList; } private set { this._uiElementList = value; } }
protected CursorGUIObject Cursor { get { return this._cursor; } private set { this._cursor = value; } }
protected int? CursorIndex
{
get { return this._cursorIndex; }
set
{
int? nullablePriorIndex = this.CursorIndex;
if (nullablePriorIndex != null)
{
int priorIndex = nullablePriorIndex.Value;
this.UIElementList.RemoveAt(priorIndex);
}
if (value == null)
{
this._cursorIndex = null;
}
else
{
int newIndex = value.Value;
if (newIndex < 0)
{
newIndex = 0;
}
else
{
int thisListCount = this.UIElementList.Count;
if (newIndex > thisListCount) { newIndex = thisListCount; }
}
this.UIElementList.Insert(newIndex, this.Cursor);
this._cursorIndex = newIndex;
}
this.UpdateDisplay();
}
}
protected void UpdateDisplay()
{
this.Children.Clear();
foreach (System.Windows.UIElement uiElement in this.UIElementList) { this.Children.Add(uiElement); }
}
}
public class CursorGUIObject
: System.Windows.Controls.WrapPanel
{
public const double MINIMUM_BLINK_TIME_IN_MS = 5;
public const double MINIMUM_HEIGHT = 0.5;
public const double MINIMUM_WIDTH = 0.5;
private object ToggleVisibilityLock = new object();
private delegate void TimerIntervalDelegate();
private System.Windows.Shapes.Rectangle _rectangle;
private System.Timers.Timer _timer;
private System.Windows.Threading.Dispatcher _dispatcher;
public CursorGUIObject(System.Windows.Threading.Dispatcher dispatcher, double height, double width, double blinkTimeInMS)
{
this.Dispatcher = dispatcher;
System.Windows.Shapes.Rectangle rectangle = new System.Windows.Shapes.Rectangle();
rectangle.Width = width > MINIMUM_WIDTH ? width : MINIMUM_WIDTH;
rectangle.Height = height > MINIMUM_HEIGHT ? height : MINIMUM_HEIGHT;
rectangle.Fill = System.Windows.Media.Brushes.Black;
this.Rectangle = rectangle;
this.Children.Add(rectangle);
System.Timers.Timer timer = new System.Timers.Timer(blinkTimeInMS > MINIMUM_BLINK_TIME_IN_MS ? blinkTimeInMS : MINIMUM_BLINK_TIME_IN_MS);
this.Timer = timer;
timer.Elapsed += timer_Elapsed;
timer.Start();
}
~CursorGUIObject()
{
System.Timers.Timer timer = this.Timer;
if (timer != null) { timer.Dispose(); }
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Delegate timerDelegate = new TimerIntervalDelegate(ToggleVisibility);
this.Dispatcher.BeginInvoke(timerDelegate);
}
protected void ToggleVisibility()
{
lock (ToggleVisibilityLock)
{
if (this.Rectangle.Visibility.Equals(System.Windows.Visibility.Hidden))
{
this.Rectangle.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Rectangle.Visibility = System.Windows.Visibility.Hidden;
}
}
}
protected System.Windows.Shapes.Rectangle Rectangle { get { return this._rectangle; } private set { this._rectangle = value; } }
protected System.Timers.Timer Timer { get { return this._timer; } private set { this._timer = value; } }
protected System.Windows.Threading.Dispatcher Dispatcher { get { return this._dispatcher; } private set { this._dispatcher = value; } }
}
Pretty much all WPF controls provide access to the UIElement.PreviewMouseDown Event, which you can use to monitor mouse clicks. So, this event lets you monitor when each object is clicked on. Next, I'd advise you to use a small Popup control to popup a TextBox that the user could enter a value with:
<Popup Name="Popup">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5">
<TextBox Text="{Binding InputText}" />
</Border>
</Popup>
Depending on how you have set up your project, you could open the Popup from the event handler:
private void YourObject_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = true;
}
Turns out that LineGUIObject just needed to have this.Focusable = true; set in its constructor so that it could receive the keyboard's focus when clicked.
Now that it can be focused on, this.KeyUp += LineGUIObject_KeyUp; also in the constructor, and
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
this.AddText(e.Key.ToString());
}
Even this had a problem at first since my LineGUIObject was nested in a ScrollViewer which kept stealing focus immediately after the LineGUIObject would receive it. This was fixed by making the ScrollViewer to be unable to get focus, i.e. <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Focusable="False"/>.

Mouse down between two pieces of a puzzle

I am building a puzzle game in winforms, and i want to recognize the mousedown over any piece, and move it with the mousemove.
The issue is that when i touch the transparent part of the puzzle piece, i want to verify if there is any piece behind that one, and if so, start the mousemove of that other piece, got it?
I am also able to recognize if the mousedown was over the image, or if it happens in the transparent part of the puzzle piece. My problem is to get the best way to pass the mouse event to the piece behind.
Many Thanks in advance.
UPDATE 1
The following is the class for the puzzle piece:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_Offset != Point.Empty)
{
Point newlocation = this.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
this.Location = newlocation;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
_Offset = Point.Empty;
}
protected override void OnMouseDown(MouseEventArgs e)
{
Down(e);
//Console.WriteLine(color.ToString());
}
public void Down(MouseEventArgs e)
{
Bitmap b = new Bitmap(imagem);
Color? color = null;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
if (e.Button == MouseButtons.Left)
{
_Offset = new Point(e.X, e.Y);
this.BringToFront();
}
}
}
catch {
}
}
}
The following code, is my DrawingArea (Panel) that i create in order to work with transparency:
abstract public class DrawingArea : Panel
{
protected Graphics graphics;
abstract protected void OnDraw();
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
public DrawingArea()
{
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
protected override void OnPaint(PaintEventArgs e)
{
this.graphics = e.Graphics;
this.graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
this.graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
this.graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
this.graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
OnDraw();
}
}
And you can also see my Form code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
}
Those are my pieces, and when i touch the transparent space in the first piece, i want to pick up the second one and move it on mouseMouse instead of doing nothing...
It looks like this:
Apologize my bad english.
UPDATE 2
I think i am getting very close to the solution, but something strange happens now, when i touch the piece behind another one, it disappear...
What am i doing wrong?
SOME CODE UPDATES
Piece Class:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Boolean movable = false;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
public void Move(MouseEventArgs e)
{
if (_Offset != Point.Empty)
{
Point newlocation = this.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
this.Location = newlocation;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
_Offset = Point.Empty;
movable = false;
}
protected override void OnMouseDown(MouseEventArgs e)
{
Down(e);
//Console.WriteLine(color.ToString());
}
public Boolean Down(MouseEventArgs e, bool propaga=true)
{
Form parentForm = (this.Parent as Form);
Bitmap b = new Bitmap(imagem);
Color? color = null;
Boolean flag = false;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
if (e.Button == MouseButtons.Left)
{
_Offset = new Point(e.X, e.Y);
this.BringToFront();
flag = true;
movable = true;
}
}
else
{
if(propaga)
(this.Parent as Form1).propagaEvento(this, e);
flag = false;
}
return flag;
}
catch {
return flag; }
}
}
Form1:
public partial class Form1 : Form
{
private List<Peça> peças;
private Point _Offset = Point.Empty;
public Form1()
{
InitializeComponent();
peças = new List<Peça>();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
criaListaPecas();
associaEventosPecas();
}
private void associaEventosPecas()
{
foreach (Peça p in peças)
{
p.MouseMove += Form1_MouseMove;
}
}
private void criaListaPecas()
{
peças.Clear();
foreach (Control p in this.Controls)
{
if (p.GetType() == typeof(Peça))
peças.Add((Peça)p);
}
Console.WriteLine(peças[0].Name);
Console.WriteLine(peças[1].Name);
Console.WriteLine(peças[2].Name);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = e.Location.ToString();
gereMovimento(e);
}
private void gereMovimento(MouseEventArgs e)
{
foreach (Peça p in peças)
{
if (p.movable)
{
p.Move(e);
}
}
}
internal void propagaEvento(Peça peça, MouseEventArgs e)
{
foreach (Peça p in peças)
{
if (p != peça)
{
if (p.Down(e, false))
break;
}
}
}
}
Thanks in advance again :)
Pieces can be represent as:
public class Piece
{
public Point Location {get; set;}
public int Z {get; set;}
public int ID {get; set;} // to be bound to control or a control itself?
public Image Image {get; set;} // texture?
public DockStyle PlusArea {get; set;}
public DockStyle MinusArea {get; set;} // can be None
...
public bool HitTest(Point point)
{
// assuming all of same size
if((new Rectangle(Location, new Size(...)).Contains(point))
{
switch(MinusArea)
{
case Top:
if((new Rectangle(...)).Contains(point))
return false;
...
}
}
switch(MinusArea)
{
case Top:
if((new Rectangle(...)).Contains(point))
return true;
...
}
return false;
}
Then puzzle is
public class Puzzle
{
public List<Piece> Pieces {get; set;}
public void Draw(Graphics graphics)
{
// draw all pictures with respect to z order
}
public Piece HitTest(Point point)
{
... // hittest all pieces, return highest z-order or null
}
}
It is not a complete solution, but should give you idea.
Basically:
In mouse event you call Figure.HitTest() to get figure to start moving (that's what you need).
You draw everything into owner drawn control by calling Figure.Draw().
Obviously, drag-n-drop operations are calling Invalidate().
You may have special flag to indicate figure being dragged and draw it differently (with shadows, a bit offsetted to simulate it's pulled over other pieces, etc).
Each figure is represented as rectangle and PlusArea or MinusArea (don't know how to call them better, it's this extra or missing area of piece connectors), this is simplification, you can improve it.
In general, keep a list of all your puzzle piece controls, sorted top down. When you get a mouse down event on one piece, check the transparency at that point, if it it not transparent handle the event on that piece. If it is transparent forward the event to the next piece down in your list (directly calling the event handler is probably the easiest way). Keep doing this until you either find a non transparent point, or run out of pieces.
UPDATE
Here is a link to a project showing an example of how to do this in pure GDI.
https://drive.google.com/file/d/0B42fIyGTLNv3WlJwNGVRN2txTGs/edit?usp=sharing
SOLVED :)
i have figured it out...
Here's the code to anybody how needs
(I have made it right now, so the code is not clean yet):
Piece Class:
class Peça : DrawingArea
{
private Point _Offset = Point.Empty;
public Boolean movable = false;
public Image imagem
{
get;
set;
}
protected override void OnDraw()
{
Rectangle location = new Rectangle(0, 0, imagem.Width, imagem.Height);
this.graphics.DrawImage(imagem, location);
}
public Boolean Down(Point e, bool propaga = true)
{
Bitmap b = new Bitmap(imagem);
Color? color = null;
Boolean flag = false;
try
{
color = b.GetPixel(e.X, e.Y);
if (color.Value.A != 0 && color != null)
{
flag = true;
}
else
{
flag = false;
}
return flag;
}
catch
{
return flag;
}
}
}
Form1:
public partial class Form1 : Form
{
private List<Peça> peças;
private Point _Offset = Point.Empty;
private Peça peça1, peça2, peça3, peça4;
private bool canMove;
private Peça atual;
private bool other=false;
public Form1()
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
InitializeComponent();
atual = new Peça();
peça1 = new Peça();
peça2 = new Peça();
peça3 = new Peça();
peça4 = new Peça();
peça1.imagem = Properties.Resources._4p1_1;
peça2.imagem = Properties.Resources._4p1_2;
peça3.imagem = Properties.Resources._4p1_3;
peça4.imagem = Properties.Resources._4p1_4;
peças = new List<Peça>();
peça1.Name = "peça1";
peça2.Name = "peça2";
peça3.Name = "peça3";
peça4.Name = "peça4";
this.Controls.Add(peça1);
this.Controls.Add(peça2);
this.Controls.Add(peça3);
this.Controls.Add(peça4);
criaListaPecas();
foreach (Peça p in peças)
{
p.Size = new Size(p.imagem.Width, p.imagem.Height);
}
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
associaEventosPecas();
canMove = false;
}
private void associaEventosPecas()
{
foreach (Peça p in peças)
{
p.MouseMove += Form1_MouseMove;
p.MouseDown += Form1_MouseDown;
p.MouseUp += Form1_MouseUp;
}
}
private void criaListaPecas()
{
peças.Clear();
foreach (Control p in this.Controls)
{
if (p.GetType() == typeof(Peça))
peças.Add((Peça)p);
}
Console.WriteLine(peças[0].Name);
Console.WriteLine(peças[1].Name);
Console.WriteLine(peças[2].Name);
Console.WriteLine(peças[3].Name);
}
protected override CreateParams CreateParams
{
get
{
CreateParams handleParam = base.CreateParams;
handleParam.ExStyle |= 0x02000000;
return handleParam;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (sender.GetType().Equals(typeof(Peça)))
{
label1.Text = new Point(e.Location.X + (sender as Peça).Location.X, e.Location.Y + (sender as Peça).Location.Y).ToString();
}
else
label1.Text = e.Location.ToString();
gereMovimento(sender, e);
}
private void gereMovimento(object sender, MouseEventArgs e)
{
if (canMove)
{
if (other)
{
Point p = atual.PointToClient(new Point(e.X + (sender as Peça).Location.X, e.Y + (sender as Peça).Location.Y));
Point newlocation = atual.Location;
newlocation.X += p.X - _Offset.X;
newlocation.Y += p.Y - _Offset.Y;
atual.Location = newlocation;
}
else
{
Point newlocation = atual.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
atual.Location = newlocation;
}
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (sender.GetType().Equals(typeof(Peça)) && e.Button == MouseButtons.Left)
{
atual = sender as Peça;
atual.BringToFront();
criaListaPecas();
if (atual.Down(e.Location))
{
_Offset = new Point(e.X, e.Y);
canMove = true;
other = false;
}
else
{
Console.WriteLine(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)));
Console.WriteLine(atual.Location);
Point p = new Point();
if (peças[1].ClientRectangle.Contains(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[1].Down(peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[1].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[1];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
else if (peças[2].ClientRectangle.Contains(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[2].Down(peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[2].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[2];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
else if (peças[3].ClientRectangle.Contains(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y)))
&& peças[3].Down(peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y))))
{
p = peças[3].PointToClient(new Point(e.X + atual.Location.X, e.Y + atual.Location.Y));
atual = peças[3];
atual.BringToFront();
criaListaPecas();
_Offset = p;
canMove = true;
other = true;
}
}
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
canMove = false;
}
}
Apologize the repeated and confused code, but as i said, i have made it seconds ago, and did not clean the code yet ;)

How to redraw Node in TreeView (WinForms)

I need to implement own TreeView with blinked TreeNode. My prototype is:
public class BlinkTreeView : TreeView
{
private int blinkInterval;
private bool blinkState;
[Category("Behavior"), Browsable(true)]
public Icon BlinkIcon { get; set; }
[Category("Behavior"), Browsable(true)]
public Icon SelectedBlinkIcon { get; set; }
[Category("Behavior"), Browsable(true), DefaultValue(1000)]
public int BlinkInterval {
get
{
return blinkInterval;
}
set
{
blinkInterval = value;
if (value > 0)
{
blinkTimer.Interval = value;
blinkTimer.Start();
}
else
{
blinkTimer.Stop();
blinkState = false;
Invalidate();
}
}
}
private Timer blinkTimer;
public BlinkTreeView()
: base()
{
blinkTimer = new Timer();
blinkTimer.Tick += new EventHandler(blinkTimer_Tick);
blinkState = false;
this.DrawMode = TreeViewDrawMode.OwnerDrawAll;
}
void blinkTimer_Tick(object sender, EventArgs e)
{
if (BlinkInterval > 0)
{
blinkState = !blinkState;
}
else
{
blinkState = false;
}
Invalidate();
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
e.DrawDefault = true;
base.OnDrawNode(e);
if (blinkState)
{
//here i want to draw blinked item, but i can't redraw item icons and text.
}
}
}
In OnDrawNode i can't redraw icon and text of node.
Any idea how to solve this?
Just a thought, but you could invert (xor) over the item without making the tree into an owner-draw control. I think it works something like the following:
using (Graphics g = Graphics.FromHwnd(Tree.Handle))
{
TreeNode node = myBlinkyNode;
if (node != null)
{
using(Region myRegion = new Region(node.Bounds))
myRegion.Xor(xorRect);
}
}
You'll need to keep track if the blink is visible or not and handle the Paint event so that you can re-draw the inverted rectangle.
Have a timer toggle the state of the blinking nodes, i.e.:
Node.ForeColor = Node.ForeColor == Color.White ? Color.Black : Color.White;

Categories