When animating a control over a graphic, the graphic erases - c#

I am using a transparent image, from this package
https://www.codeproject.com/Articles/25048/How-to-Use-Transparent-Images-and-Labels-in-Window
I am loading the image at the start and everything is fine.
To the left of the transparent image, I am loading a normal PictureBox.
The picturebox then starts moving towards another control that is to the right of the transparent image, meaning that the picturebox passes above the transparent image (which is intended).
When the picturebox moves past the transparent image, the part that the picturebox went over is "erased".
Can anyone help keep the transparent image not erased?
Animating the control function:
public static void SlideToDestination(Control destination, Control control, int delay, Action onFinish)
{
control.BringToFront();
new Task(() =>
{
int curX = control.Left + (control.Width / 2);
int curY = control.Bottom - (control.Height / 2);
int desX = destination.Left + (destination.Width / 2);
int desY = destination.Bottom - (destination.Height / 2);
int directionX = desX > curX ? 1 : -1;
int directionY = desY > curY ? 1 : -1;
while (curX != desX || curY != desY)
{
try
{
if (curX != desX)
{
control.Invoke((Action)delegate ()
{
control.Left += directionX;
});
}
if (curY != desY)
{
control.Invoke((Action)delegate ()
{
control.Top += directionY;
});
}
Thread.Sleep(delay);
}
catch
{
// form could be disposed
break;
}
curX = control.Left + (control.Width / 2);
curY = control.Bottom - (control.Height / 2);
}
if (onFinish != null) onFinish();
}).Start();
}
Before Image:
After Image:

I figured out the solution. In the SlideToDestination function, after changing the location, all I needed to do was call Form1.snake.Update() where snake is the transparent image control. Then it redraws the snake every time after updating the location.

Related

Show tooltip for part of user control

I'm building custom control that will display tiles, like colored grid.
I've managed to do drawing, scrolling and basic logic, but I have problem with creating tooltip for each tile.
Each tile color depends on data that is "bound" to that tile.
I'll try to describe my idea:
Above image shows my control, I have 4 squares drawn there, I'd like to show different tooltip when user hovers different parts of my control.
Below is my tooltip (small red rectangle) and lower is standard WinForms Chart component.
I'd like to get this kind of behavior in my control (tooltip is outside of control, so long text is displayed properly).
Below is code of my control with just basic functionality:
public sealed class UC1 : UserControl
{
private bool _showTooltip;
private string _tooltipText;
private Point _mousePosition;
public UC1()
{
MinimumSize = new Size(100, 100);
Size = new Size(100, 100);
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LightGoldenrodYellow, 0, 0, Width/2, Height/2);
e.Graphics.FillRectangle(Brushes.LightGray, Width/2, 0, Width, Height/2);
e.Graphics.FillRectangle(Brushes.LightSlateGray, 0, Height/2, Width/2, Height);
e.Graphics.FillRectangle(Brushes.LightSteelBlue, Width/2, Height/2, Width, Height);
using (var p = new Pen(Color.Black, 2))
{
e.Graphics.DrawLine(p, Width/2, 0, Width/2, Height);
e.Graphics.DrawLine(p, 0, Height/2, Width, Height/2);
}
if (_showTooltip)
{
SizeF c = e.Graphics.MeasureString(_tooltipText, DefaultFont);
int width = (int) c.Width;
int height = (int) c.Height;
const int offset = 12;
var x = _mousePosition.X + width + offset > Width ? _mousePosition.X - width : _mousePosition.X + offset;
var y = _mousePosition.Y + height + offset > Height ? _mousePosition.Y - height : _mousePosition.Y + offset;
e.Graphics.FillRectangle(Brushes.Red, x, y, width, height);
e.Graphics.DrawString(_tooltipText, DefaultFont, Brushes.Black, x, y);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.X > 0 && e.X < Width/2 && e.Y > 0 && e.Y < Height/2)
{
Debug.WriteLine("1,1 square");
_tooltipText = "1,1";
}
else if (e.X > Width/2 && e.X < Width && e.Y > 0 && e.Y < Height/2)
{
Debug.WriteLine("1,2 square");
_tooltipText = "1,2";
}
else if (e.X > 0 && e.X < Width/2 && e.Y > Height/2 && e.Y < Height)
{
Debug.WriteLine("2,1 square");
_tooltipText = "2,1";
}
else if (e.X > Width/2 && e.X < Width && e.Y > Height/2 && e.Y < Height)
{
Debug.WriteLine("2,2 square");
_tooltipText = "2,2";
}
_mousePosition = e.Location;
_showTooltip = true;
Refresh();
}
protected override void OnMouseLeave(EventArgs e)
{
_showTooltip = false;
Refresh();
base.OnMouseLeave(e);
}
}
I've found similar question: How to make a floating (tooltip) control in Windows.Forms? but I'd like to avoid creating custom tooltip and instead use standard one.
How can I show standard tooltip that will change it text when hovering on different tiles. I'd like to add everything to my user control, so I won't have to add any code to form hosting my control.
Why do you try to draw the ToolTip yourself instead of using the system one?
Just add one to the UC class
// private bool _showTooltip; ?? probably not needed any more..
private string _tooltipText;
// private Point _mousePosition; ??..
ToolTip ttip = new ToolTip();
and set it like this:
// _mousePosition = e.Location; ??..
// _showTooltip = true; ??..
ttip.SetToolTip(this, _tooltipText); // use this in the mousemove
Refresh();
Of course now you can skip the whole Painting part..
If you want to control the location where the ToolTip is shown use one of the ShowToolTip() overloads instead of SetToolTip() ..!
While both are still there this is the result, going over the UC's border and displaying with a nice drop shadow..:
If you really want your ToolTip to look different from the usual ones, you can set its OwnerDraw to true and code its Draw event just like any other control with GDI+ graphics methods..
Update:
There is an inherent flicker problem; for an explanation see Hans' answer here; his recommendation #2 and one of the answers is helpful:
Remember last mouse position and set the tooltip only when the mouse
position changes.
So we need to add a last ToolTip location:
Point tipLoc = Point.Empty;
Which we test and set in the mouse move:
if (tipLoc != e.Location )
{
tipLoc = e.Location;
ttip.SetToolTip(this, _tooltipText);
}

Scale and dragging image inside canvas only in windows store app

I have been struggling for the past week in transformation of image within the limits of canvas
void image_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var name = (Image)sender;
var translate = (CompositeTransform)name.RenderTransform;
var newPosX = Canvas.GetLeft(name) + translate.TranslateX + e.Delta.Translation.X;
var newPosY = Canvas.GetTop(name) + translate.TranslateY + e.Delta.Translation.Y;
if (!isBoundary(newPosX, DrawCanvas.ActualWidth - name.ActualWidth, 0))
translate.TranslateX += e.Delta.Translation.X;
if (!isBoundary(newPosY, DrawCanvas.ActualHeight - name.ActualHeight, 0))
translate.TranslateY += e.Delta.Translation.Y;
}
bool isBoundary(double value, double max, double min)
{
return value > max ? true : value < min ? true : false;
}
This is my dragging code it works fine and also isBoundary function limits the image inside canvas inly but when I am scaling with this code
{
var translate = (CompositeTransform)name.RenderTransform;
translate.CenterX = name.ActualWidth / 2;
translate.CenterY = name.ActualHeight / 2;
translate.ScaleX += e.Delta.Translation.X*0.001 ;
translate.ScaleY += e.Delta.Translation.Y*0.001;
}
Scale is also done but not limiting inside canvas and I when I scale my image to make big its canvas is also scaling and when I am shriking in size its canvas (drag) area is also shrinking.
Need help in limiting scaling and its canvas problem.

Selecting part of image on windows form

I'm making instrument to select part of image. I have PictrureBox, and simple way to make it :
void StartPanel(object sender, MouseEventArgs args)
{
xStart = args.X;
yStart = args.Y;
panelStarted = true;
pan.Location = new Point(xStart, yStart);
}
void FinishPanel(object sender, MouseEventArgs args)
{
xFinish = args.X;
yFinish = args.Y;
panelStarted = false;
}
void UpdatePanel(object sender, MouseEventArgs args)
{
if (panelStarted)
{
int x = args.X;
int y = args.Y;
int newxstart = xStart;
int newystart = yStart;
int neww = 0;
int newh = 0;
if (x >= xStart)
neww = x - xStart;
else
{
neww = xStart - x;
newxstart = x;
}
if (y >= yStart)
newh = y - yStart;
else
{
newh = yStart - y;
newystart = y;
}
pan.Size = new Size(neww, newh);
pan.Location = new Point(newxstart, newystart);
}
}
When I move mouse right and down, it is absolutely ok. But when I move it left or up I can see blinks at my area. So I have understood, that it is because when I move mouse left or up, my panel is redrawed, because Panel.Location is changed, and when I move mouse right and down, location is not changed, only size is changed, so it is not redrawed, just some pixels are added to panel. What is standart solution for this?
It's not easy trying to see what you are trying to do, but I guess you are using a panel as a draggable control to drag over the picturebox surface capturing the portion of image below (like a lens) - yes?
If so, then this is not the best way to do it. It is better to just draw a rectangle on the picturebox surface and "drag" that around - this is simple with just using the mouse events to sets the top left corner and use the onpaint to draw the unfilled rectangle over the image. Capturing the image when you are ready is simple too using whatever event you wish, then copy the image giving the same positions to the new bitmap.
Putting one control over another often causes flickers - even with double buffering. It also takes far more code.
Since you are describing a drawing issue when resizing the panel, probably the easiest fix is to replace the panel you are using with one that is double buffered and will invalidate on resize a event:
public class BufferedPanel : Panel {
public BufferedPanel() {
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
}

How to use ScrollableControl with AutoScroll set to false

I have a custom control that zooms on a custom drawn document canvas.
I tried using AutoScroll but it was not giving satisfactory results. When I would set AutoScrollPosition and AutoScrollMinSize back to back (in any order) it would force a paint and cause jitter each time the zoom changes. I assume this was because it was calling an Update and not Invalidate when I modified both properties.
I am now manually setting the HorizontalScroll and VerticalScroll properties with AutoScroll set to false like so each time the Zoom level or the client size changes:
int canvasWidth = (int)Math.Ceiling(Image.Width * Zoom) + PageMargins.Horizontal;
int canvasHeight = (int)Math.Ceiling(Image.Height * Zoom) + PageMargins.Vertical;
HorizontalScroll.Maximum = canvasWidth;
HorizontalScroll.LargeChange = ClientSize.Width;
VerticalScroll.Maximum = canvasHeight;
VerticalScroll.LargeChange = ClientSize.Height;
if (canvasWidth > ClientSize.Width)
{
HorizontalScroll.Visible = true;
}
else
{
HorizontalScroll.Visible = false;
HorizontalScroll.Value = 0;
}
if (canvasHeight > ClientSize.Height)
{
VerticalScroll.Visible = true;
}
else
{
VerticalScroll.Visible = false;
VerticalScroll.Value = 0;
}
int focusX = (int)Math.Floor((FocusPoint.X * Zoom) + PageMargins.Left);
int focusY = (int)Math.Floor((FocusPoint.Y * Zoom) + PageMargins.Top);
focusX = focusX - ClientSize.Width / 2;
focusY = focusY - ClientSize.Height / 2;
if (focusX < 0)
focusX = 0;
if (focusX > canvasWidth - ClientSize.Width)
focusX = canvasWidth - ClientSize.Width;
if (focusY < 0)
focusY = 0;
if (focusY > canvasHeight - ClientSize.Height)
focusY = canvasHeight - ClientSize.Height;
if (HorizontalScroll.Visible)
HorizontalScroll.Value = focusX;
if (VerticalScroll.Visible)
VerticalScroll.Value = focusY;
In this case, FocusPoint is a PointF structure that holds the coordinates in the bitmap which the user is focused on (for example, when they mouse wheel to zoom in they are focusing on the current mouse location at that time). This functionality works for the most part.
What does not work is the scroll bars. If the user tries to manually scroll by clicking on either scroll bar, they both keep returning to 0. I do not set them anywhere else in my code. I have tried writing the following in the OnScroll() method:
if (se.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
VerticalScroll.Value = se.NewValue;
}
else
{
HorizontalScroll.Value = se.NewValue;
}
Invalidate();
But this causes some very erratic behavior including flicking and scrolling out of bounds.
How am I supposed to write the code for OnScroll? I've tried the base.OnScroll but it didn't do anything while AutoScroll is set to false.
I ended up implementing my own custom scrolling by creating 3 child controls: an HScrollBar, a VScrollBar, and a Panel.
I hide ClientSize and ClientRectangle like so:
public new Rectangle ClientRectangle
{
get
{
return new Rectangle(new Point(0, 0), ClientSize);
}
}
public new Size ClientSize
{
get
{
return new Size(
base.ClientSize.Width - VScrollBar.Width,
base.ClientSize.Height - HScrollBar.Height
);
}
}
The layout is done in OnClientSizeChanged:
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
HScrollBar.Location = new Point(0, base.ClientSize.Height - HScrollBar.Height);
HScrollBar.Width = base.ClientSize.Width - VScrollBar.Width;
VScrollBar.Location = new Point(base.ClientSize.Width - VScrollBar.Width, 0);
VScrollBar.Height = base.ClientSize.Height - HScrollBar.Height;
cornerPanel.Size = new Size(VScrollBar.Width, HScrollBar.Height);
cornerPanel.Location = new Point(base.ClientSize.Width - cornerPanel.Width, base.ClientSize.Height - cornerPanel.Height);
}
Each ScrollBar has their Scroll event subscribed to the following:
private void ScrollBar_Scroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
}
And finally we can allow MouseWheel events to scroll with the following:
protected override void OnMouseWheel(MouseEventArgs e)
{
int xOldValue = VScrollBar.Value;
if (e.Delta > 0)
{
VScrollBar.Value = (int)Math.Max(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), 0);
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
else
{
VScrollBar.Value = (int)Math.Min(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), VScrollBar.Maximum - (VScrollBar.LargeChange - 1));
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
}
For custom painting, you would use the following statement:
e.Graphics.TranslateTransform(-HScrollBar.Value, -VScrollBar.Value);
This worked perfectly without the glitches present when using AutoScroll.

Problem with the scrolling!

I have implemented a picturebox in my in my form.I even implemented the scrollbars to make the image fit in it.so now the problem is when i try to scroll the button down,it scrolls down and immediately when i leave the mouse the button scrolls up ..here is the code implemnted please give some suggestions..
public void DisplayScrollBars()
{
// If the image is wider than the PictureBox, show the HScrollBar.
if (pictureBox1.Width > pictureBox1.Image.Width - this.vScrollBar1.Width)
{
hScrollBar1.Visible = false;
}
else
{
hScrollBar1.Visible = true;
}
// If the image is taller than the PictureBox, show the VScrollBar.
if (pictureBox1.Height >
pictureBox1.Image.Height - this.hScrollBar1.Height)
{
vScrollBar1.Visible = false;
}
else
{
vScrollBar1.Visible = true;
}
}
private void HandleScroll(Object sender, ScrollEventArgs se)
{
/* Create a graphics object and draw a portion
of the image in the PictureBox. */
Graphics g = pictureBox1.CreateGraphics();
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
new Rectangle(hScrollBar1.Value, vScrollBar1.Value,
pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
GraphicsUnit.Pixel);
pictureBox1.Update();
}
public void SetScrollBarValues()
{
// Set the Maximum, Minimum, LargeChange and SmallChange properties.
this.vScrollBar1.Minimum = 0;
this.hScrollBar1.Minimum = 0;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width) > 0)
{
this.hScrollBar1.Maximum =
this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width;
}
// If the VScrollBar is visible, adjust the Maximum of the
// HSCrollBar to account for the width of the VScrollBar.
if (this.vScrollBar1.Visible)
{
this.hScrollBar1.Maximum += this.vScrollBar1.Width;
}
this.hScrollBar1.LargeChange = this.hScrollBar1.Maximum / 10;
this.hScrollBar1.SmallChange = this.hScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.hScrollBar1.Maximum += this.hScrollBar1.LargeChange;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height) > 0)
{
this.vScrollBar1.Maximum =
this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height;
}
// If the HScrollBar is visible, adjust the Maximum of the
// VSCrollBar to account for the width of the HScrollBar.
if (this.hScrollBar1.Visible)
{
this.vScrollBar1.Maximum += this.hScrollBar1.Height;
}
this.vScrollBar1.LargeChange = this.vScrollBar1.Maximum / 10;
this.vScrollBar1.SmallChange = this.vScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.vScrollBar1.Maximum += this.vScrollBar1.LargeChange;
}
private void pictureBox1_Resize(object sender, EventArgs e)
{
// If the PictureBox has an image, see if it needs
// scrollbars and refresh the image.
if (pictureBox1.Image != null)
{
this.DisplayScrollBars();
this.SetScrollBarValues();
this.Refresh();
}
}
As I comment above, the correct way of doing this is to put your pictureBox in a panel and set the panel.AutoScroll=true. Also you need to set pictureBox.SizeMode=AutoSize so it is sized equal to the size of the image that it contains. Check: PictureBoxSizeMode Enumeration

Categories

Resources