Draggable Images - c#

I have draggable images on my Windows Form. The PNG's themselves have transparent backgrounds yet it only matches the panel's background color when I load it. There are other labels with different colors I'd like to be able to see as I drag it instead. Anyone know what to use to make this happen?
http://postimg.org/image/d8p4s53pf/
EDIT:
Trying to make the PictureBox truely transparant. The drag stuff is done, but the background is just the background of the panel, and doesn't show controls it passes over.
I used this but it's a bit glitchy.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}

Dragable Controls as such are not hard in WinForms. But once they need transparency the options are rather restricted: Each must be completely contained in its Parent; this implies that they all must be nested. No problem to create a set of transparent layers like you have in a graphics program.
But moving even one PictureBox-Pawn across a board full of others simply will not work with WinForms Controls.
But your aim is simple enough to do without any moving controls.
Instead you can simply draw the chess pieces onto the control surface in the Paint event. I have implemented a rudimentary solution and here is the Paint event from it:
List<ChessPiece> pieces = new List<ChessPiece>();
int mpIndex = -1; // index of the moving piece
Rectangle mmr; // rectangle where moving piece is drawn
// board display variables
int pieceSize = 60;
int tileSize = 80;
int borderWidth = 50;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach(ChessPiece p in pieces)
{
if (!p.onBoard) continue; // skip the piece?
e.Graphics.DrawImage(imageList1.Images[p.pieceIndex],
borderWidth + p.col * tileSize,
borderWidth + p.row * tileSize);
}
// if we have a moving piece..
if (mpIndex >= 0 && !pieces[mpIndex].onBoard)
e.Graphics.DrawImage(imageList1.Images[pieces[mpIndex].pieceIndex],
mmr.Location);
}
As you can see it really isn't very long. It makes use of a few obvious variables and a few less obvious ones. The pieces list and the ImageList must be prepared up front. The rectangle mmr is set in the MouseMove:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
mmr = new Rectangle(e.X - pieceSize / 2,
e.Y - pieceSize / 2, pieceSize, pieceSize);
pictureBox1.Invalidate(); // trigger the painting
}
}
I won't post the MouseUp and -Down events as they contain mostly code that doesn't actually pertain to moving transparent images but to managing the list of pieces..
The main idea is to draw all images from a list of pieces, unless a piece is not on the board. The moving piece is drawn separately, its coordinates are not from the board positions but from the mouse position.
If in the MouseUp event we find that the the move is illegal, we can simply set it back onBoard without changing the position and it will be drawn at its old place.
In the MouseDown we determine the piece clicked on, set its onBoard=false and set the moving index mpIndex.
The class for the pieces is not long or complicated either;
public class ChessPiece
{
public bool onBoard { get; set; } // is the piece standing on the board?
public int row { get; set; } // row
public int col { get; set; } // column
public char piecetype { get; set; } // a little simplistic..not used..
public char color { get; set;} // .. could be an enumeration!
public int pieceIndex { get; set; } // points into imageList with 12 images
public ChessPiece(char color, char type, int r, int c, int ind)
{ onBoard = true; piecetype = type; this.color = color;
row = r; col = c; pieceIndex = ind; }
}
All in all the full code is around 180 lines, uncommented but including 60 lines of setting up the pieces alone and without any chess logic..
Here are the first 3 lines of the code to create the pieces:
void makePieces()
{
ChessPiece
p = new ChessPiece('W', 'R', 7, 0, 8); pieces.Add(p);
p = new ChessPiece('W', 'N', 7, 1, 10); pieces.Add(p);
p = new ChessPiece('W', 'B', 7, 2, 9); pieces.Add(p);
..
So, moving a transparent Png graphic over other Png graphics and a board image is rather simple..
Here is a screenshot that shows a black bishop over a white knight:

Related

Accessing Graphics from Different Methods?

I'm trying to access the variable "g" from a different method, it does not throw an error but doesn't write to the screen. Any way to use the graphics class from different methods? Paint Method:
public static Graphics g;
private void paintClass(object sender, PaintEventArgs e)
{
e.Graphics = g;
}
Method Im Trying To Access "g" From:
public void drawline(){
g.DrawLine(new Pen(Color.AliceBlue), 10, 10, 20, 20);
}
Note These Code Snippets Are from The Same Class
If this isn't possible, I am just trying to figure out a way to paint to the screen from a method that isn't the paint method.
Instead of storing it, pass it directly from your event:
private void paintClass(object sender, PaintEventArgs e)
{
drawline(e.Graphics);
}
public void drawline(Graphics g){
g.DrawLine(new Pen(Color.AliceBlue), 10, 10, 20, 20);
}
This approach would make more sense if the "draw" method was part of a different class. For example, if you had a collection of custom class instances and you pass the graphics to each one so they can each draw themselves onto a surface (with the graphics provided from the paint event of that surface).
I am guessing you could obtain what you want by using CreateGraphics().
var g = myButton.CreateGraphics();
g.DrawLine(Pens.Red, 0, 0, 20, 20);
Replace myButton with the Control or Form you want to draw on.
This may turn out to not be the right approach though. The drawing will disappear e.g. when the form is minimized, maximized, gets covered by another form etc.
The recommended pattern is to use the Paint event - see https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.paint?view=windowsdesktop-6
One thing you could try is that every time you call something like the drawline() method that you mention in your post, add this action to a List. Then, when the List changes, have your paint class fire a Refresh event that the containing control can use to ensure the canvas will be invalidated. When the control calls Refresh() on itself, the OnPaint override is called and the items in the list are iterated and drawn (or in some cases redrawn) in a legitimate Graphics context that doesn't rely on the temporary fix of calling CreateGraphics ad hoc. You could then make these calls from your main form:
Diag Button - Diagonal line adding 25 to offset each time.
private void buttonDiag_Click(object sender, EventArgs e)
{
_paintClass.DrawDiagonal(GetNextTestColor(), offsetX: 25 * _testCountDiag++);
}
Clicking the button 3x executes the Drawline with a different random known color each time.
Line Button - Draw a line between two points.
private void buttonLine_Click(object sender, EventArgs e)
{
var offsetY = 15 * _testCountLine++;
_paintClass.Drawline(
GetNextTestColor(),
new PointF(0, 100 + offsetY),
new PointF(ClientRectangle.Width, 100 + offsetY));
}
Under the hood
The PaintClass inherits List<PaintClassContext> where:
class PaintClassContext
{
public PaintOp PaintOp { get; set; }
public Color Color { get; set; }
public PointF Start { get; set; }
public PointF End { get; set; }
public float OffsetX { get; set; }
public float OffsetY { get; set; }
}
enum PaintOp{ DrawLine, Clear, DrawDiagonal}
Example: Implementation of DrawDiagonal
Add a new context to the current list.
public void DrawDiagonal(Color color, float offsetX = 0, float offsetY = 0) =>
Add(new PaintClassContext
{
PaintOp = PaintOp.DrawDiagonal,
Color = color,
OffsetX = offsetX,
OffsetY = offsetY,
});
Every time a new action is added, the Refresh event is fired and this repaints the canvas.
public new void Add(PaintClassContext context)
{
base.Add(context);
_modified = true;
Refresh?.Invoke(this, EventArgs.Empty);
}
The MainForm will be subscribed to the PaintClass.Refresh event and calls Refresh().
_paintClass.Refresh += (sender, e) =>
{
// Causes the control to repaint.
Refresh();
Text = CurrentTestColor.ToString();
};
This causes the control to redraw, where the PaintAll will iterate its current items and paint them in a legitimate graphics context rather than trying to "finesse" one.
PaintClass _paintClass = new PaintClass();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
_paintClass.PaintAll(e.Graphics, always: false);
}

Unity3D - Displaying a message at the border of the screen

I want to display a kind of banner everytime the player enters a new location. This should be at the very left border of the screen. My problem is that my current solution behaves inconsistently across resolutions. On higher resolutions the banner is positioned further to the right than needed.
I also use a piece of code to properly position the text every time it is displayed.
public class AreaName : MonoBehaviour
{
private static AreaName instance;
void Start ()
{
if (instance)
Destroy(this);
else
instance = this;
BannerBase = transform.FindChild("BannerBase").GetComponent<Image>();
NameDisplay = BannerBase.transform.FindChild("BannerText").GetComponent<Text>();
}
private Image BannerBase;
private Text NameDisplay;
public static void Display (string areatitle)
{
int width = TextWidth(instance.NameDisplay, areatitle);
instance.BannerBase.rectTransform.position = new Vector3(-305 + width, instance.BannerBase.transform.position.y, 0);
instance.NameDisplay.text = areatitle;
print(width);
}
private static int TextWidth (Text obj, string text)
{
int totalLength = 0;
Font myFont = obj.font; //chatText is my Text component
CharacterInfo characterInfo = new CharacterInfo();
char[] arr = text.ToCharArray();
foreach (char c in arr)
{
myFont.GetCharacterInfo(c, out characterInfo, obj.fontSize);
totalLength += characterInfo.advance;
}
return totalLength;
}
}
Are there any simple errors that I am overseeing? Or am I just understanding the Unity UI system wrong?
You shouldn't need to code nothing to do that.
You have put your banner at the lower left corner of the screen, with a (0,0) pivot, that's all right, but, why is it positioned at x: -320?
If you put your banner at -320 for some reason, at native resolution it probably will work as expected, but if you have activated the Canvas Resizer, at other resolutions it will be moved since is not positioned at the corner at all.

Erasing already drawn shapes [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I am coding my own Paint in C# using the System.Drawing namespace and basically everything is going well so far except for one thing, the eraser tool.
Right now I the eraser works just by drawing a line with the same color as the background so it appears like it's erasing something.
However, I want to make a new Eraser tool, perhaps improved. This new Eraser would be able to DELETE an element with a single click if the click is within it's bounds.
I know that if one thing has already been drawn it's there and nothing can be done but I was thinking about creating a string array and I'm going to add new elements to the array. For example when I add a line and a rectangle the first two elements of array would be:
line startPoint endPoint
rectangle height width x y
Something like that. And when the Erase tool is used, just compare the coordinates.
Is there an easier approach to this?
Thanks a lot!
Yes, there is. What you're planning to do is essentially retained mode rendering in a way. You keep a list (or other data structure) of objects that are on the canvas and you can reorder or change that list in any way, e.g. by removing or adding objects. After that you just re-create the drawing, that is, clear your drawing area and then draw each object in your list in order. This is necessary because once you have drawn something you only have pixels, and if your line and rectangle intersect, you may have trouble separating line pixels from rectangle pixels.
With GDI+ this is the only approach, since you don't get much more than a raw drawing surface. However, other things exist which already provide that rendering model for you, e.g. WPF.
However, a string[] is a horrible way of solving this. Usually you would have some kind of interface, e.g.
public interface IShape {
public void Draw(Graphics g);
public bool IsHit(PointF p);
}
which your shapes implement. A line would keep its stroke colour and start/end coordinates as state which it would then use to draw itself in the Draw method. Furthermore, when you want to click on a shape with the eraser, you'd have the IsHit method to determine whether a shape was hit. That way each shape is responsible for its own hit-testing. E.g a line could implement a little fuzziness so that you can click a little next to the line instead of having to hit a single pixel exactly.
That's the general idea, anyway. You could expand this as necessary to other ideas. Note that by using this approach your core code doesn't have to know anything about the shapes that are possible (comparing coordinates can be a bit cumbersome if you have to maintain an ever-growing switch statement of different shapes). Of course, for drawing those shapes you still need a bit more code because lines may need a different interaction than rectangles, ellipses or text objects.
I created a small sample that outlines above approach here. The interesting parts are as follows:
interface IShape
{
Pen Pen { get; set; }
Brush Fill { get; set; }
void Draw(Graphics g);
bool IsHit(PointF p);
}
class Line : IShape
{
public Brush Fill { get; set; }
public Pen Pen { get; set; }
public PointF Start { get; set; }
public PointF End { get; set; }
public void Draw(Graphics g)
{
g.DrawLine(Pen, Start, End);
}
public bool IsHit(PointF p)
{
// Find distance to the end points
var d1 = Math.Sqrt((Start.X - p.X) * (Start.X - p.X) + (Start.Y - p.Y) * (Start.Y - p.Y));
var d2 = Math.Sqrt((End.X - p.X) * (End.X - p.X) + (End.Y - p.Y) * (End.Y - p.Y));
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
var dx = End.X - Start.X;
var dy = End.Y - Start.Y;
var length = Math.Sqrt(dx * dx + dy * dy);
var distance = Math.Abs(dy * p.X - dx * p.Y + End.X * Start.Y - End.Y * Start.X) / Math.Sqrt(dy * dy + dx * dx);
// Make sure the click was really near the line because the distance above also works beyond the end points
return distance < 3 && (d1 < length + 3 && d2 < length + 3);
}
}
public partial class Form1 : Form
{
private ObservableCollection<IShape> shapes = new ObservableCollection<IShape>();
private static Random random = new Random();
public Form1()
{
InitializeComponent();
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
shapes.CollectionChanged += Shapes_CollectionChanged;
}
private void Shapes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Redraw();
}
public void Redraw()
{
using (var g = Graphics.FromImage(pictureBox1.Image))
{
foreach (var shape in shapes)
{
shape.Draw(g);
}
}
pictureBox1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
shapes.Add(new Line
{
Pen = Pens.Red,
Start = new PointF(random.Next(pictureBox1.Width), random.Next(pictureBox1.Height)),
End = new PointF(random.Next(pictureBox1.Width), random.Next(pictureBox1.Height))
});
}
private void pictureBox1_SizeChanged(object sender, EventArgs e)
{
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Redraw();
}
private IShape FindShape(PointF p)
{
// Reverse search order because we draw from bottom to top, but we need to hit-test from top to bottom.
foreach (var shape in shapes.Reverse())
{
if (shape.IsHit(p))
return shape;
}
return null;
}
private void button1_MouseClick(object sender, MouseEventArgs e)
{
var shape = FindShape(e.Location);
if (shape != null)
{
shape.Pen = Pens.Blue;
Redraw();
}
}
}
Clicking the button creates a random line and redraws. Redrawing is as simple as
public void Redraw()
{
using (var g = Graphics.FromImage(pictureBox1.Image))
{
foreach (var shape in shapes)
{
shape.Draw(g);
}
}
pictureBox1.Invalidate();
}
Clicking the picture will try finding a shape at the click point, and if it finds one, it colours it blue (along with a redraw). Finding the item works as follows:
private IShape FindShape(PointF p)
{
// Reverse search order because we draw from bottom to top, but we need to hit-test from top to bottom.
foreach (var shape in shapes.Reverse())
{
if (shape.IsHit(p))
return shape;
}
return null;
}
So as you can see, the fundamental parts of actually drawing things and maybe selecting them again, are fairly easy that way. Of course, the different drawing tools are another matter, although that has solutions, too.

Windows Form color changing

So i am attempting to make a MasterMind program as sort of exercise.
Field of 40 picture boxes (line of 4, 10 rows)
6 buttons (red, green, orange, yellow, blue, purple)
When i press one of these buttons (lets assume the red one) then a picture box turns red.
My question is how do i iterate trough all these picture boxes?
I can get it to work but only if i write :
And this is offcourse no way to write this, would take me countless of lines that contain basicly the same.
private void picRood_Click(object sender, EventArgs e)
{
UpdateDisplay();
pb1.BackColor = System.Drawing.Color.Red;
}
Press the red button -> first picture box turns red
Press the blue button -> second picture box turns blue
Press the orange button -> third picture box turns orange
And so on...
Ive had a previous similar program that simulates a traffic light, there i could assign a value to each color (red 0, orange 1, green 2).
Is something similar needed or how exactly do i adress all those picture boxes and make them correspond to the proper button.
Best Regards.
I wouldn't use controls, instead you can use a single PictureBox and handle the Paint event. This lets you draw inside that PictureBox so you can quickly handle all your boxes.
In code:
// define a class to help us manage our grid
public class GridItem {
public Rectangle Bounds {get; set;}
public Brush Fill {get; set;}
}
// somewhere in your initialization code ie: the form's constructor
public MyForm() {
// create your collection of grid items
gridItems = new List<GridItem>(4 * 10); // width * height
for (int y = 0; y < 10; y++) {
for (int x = 0; x < 4; x++) {
gridItems.Add(new GridItem() {
Bounds = new Rectangle(x * boxWidth, y * boxHeight, boxWidth, boxHeight),
Fill = Brushes.Red // or whatever color you want
});
}
}
}
// make sure you've attached this to your pictureBox's Paint event
private void PictureBoxPaint(object sender, PaintEventArgs e) {
// paint all your grid items
foreach (GridItem item in gridItems) {
e.Graphics.FillRectangle(item.Fill, item.Bounds);
}
}
// now if you want to change the color of a box
private void OnClickBlue(object sender, EventArgs e) {
// if you need to set a certain box at row,column use:
// index = column + row * 4
gridItems[2].Fill = Brushes.Blue;
pictureBox.Invalidate(); // we need to repaint the picturebox
}
I would use a panel as the container control for all the pic boxes, then:
foreach (PictureBox pic in myPanel.Controls)
{
// do something to set a color
// buttons can set an enum representing a hex value for color maybe...???
}
I wouldn't use pictureboxes, but instead would use a single picturebox, drawing directly onto it using GDI. The result is a lot faster, and it will set you up to write more complex games involving sprites and animation ;)
It's very easy to learn how.

Drop shadow in Winforms Controls?

is there a way to add a drop shadow to controls?
are there any controls out there with this feature?
You have to overwrite the CreateParamsproperty like this:
private const int CS_DROPSHADOW = 0x00020000;
protected override CreateParams CreateParams
{
get
{
// add the drop shadow flag for automatically drawing
// a drop shadow around the form
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
This question has been around for 6 years and needs an answer. I hope that anyone who needs to do this can extrapolate an answer for any control set from my solution. I had a panel and wanted to draw a drop shadow underneath every child control - in this instance one or more panels (but the solution should hold good for other control types with some minor code changes).
As the drop shadow for a control has to be drawn on the surface of that control's container we start by adding a function to the container's Paint() event.
Container.Paint += dropShadow;
dropShadow() looks like this:
private void dropShadow(object sender, PaintEventArgs e)
{
Panel panel = (Panel)sender;
Color[] shadow = new Color[3];
shadow[0] = Color.FromArgb(181, 181, 181);
shadow[1] = Color.FromArgb(195, 195, 195);
shadow[2] = Color.FromArgb(211, 211, 211);
Pen pen = new Pen(shadow[0]);
using (pen)
{
foreach (Panel p in panel.Controls.OfType<Panel>())
{
Point pt = p.Location;
pt.Y += p.Height;
for (var sp = 0; sp < 3; sp++)
{
pen.Color = shadow[sp];
e.Graphics.DrawLine(pen, pt.X, pt.Y, pt.X + p.Width - 1, pt.Y);
pt.Y++;
}
}
}
}
Clearly you can pick a different control type from the container's collection and you can vary the colour and depth of the shadow with some minor tweaks.
The top answer does in fact generate a shadow, but I personally wasn't satisfied with it for a few reasons:
It only works for rectangles (granted, WinForms controls are all rectangles, but we might want to use this in other cases)
More importantly: It's not smooth. It doesn't look as natural as other shadows in other programs look.
Finally, it's slightly annoying to configure.
So, because of all these things, I ended up writing my own for my project and I thought I'd share it here:
public partial class Form1 : Form
{
List<Control> shadowControls = new List<Control>();
Bitmap shadowBmp = null;
public Form1()
{
InitializeComponent();
shadowControls.Add(panel1);
this.Refresh();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (shadowBmp == null || shadowBmp.Size != this.Size)
{
shadowBmp?.Dispose();
shadowBmp = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);
}
foreach (Control control in shadowControls)
{
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddRectangle(new Rectangle(control.Location.X, control.Location.Y, control.Size.Width, control.Size.Height));
DrawShadowSmooth(gp, 100, 60, shadowBmp);
}
e.Graphics.DrawImage(shadowBmp, new Point(0, 0));
}
}
private static void DrawShadowSmooth(GraphicsPath gp, int intensity, int radius, Bitmap dest)
{
using (Graphics g = Graphics.FromImage(dest))
{
g.Clear(Color.Transparent);
g.CompositingMode = CompositingMode.SourceCopy;
double alpha = 0;
double astep = 0;
double astepstep = (double)intensity / radius / (radius / 2D);
for (int thickness = radius; thickness > 0; thickness--)
{
using (Pen p = new Pen(Color.FromArgb((int)alpha, 0, 0, 0), thickness))
{
p.LineJoin = LineJoin.Round;
g.DrawPath(p, gp);
}
alpha += astep;
astep += astepstep;
}
}
}
}
In this implementation, all Controls added to the shadowControls will be painted with a smooth shadow. You should be able to implement this for non-rectangular shapes because the main function to generate the shadows takes a GraphicsPath. Please note that it's important you draw the shadow to another bitmap before drawing it to the form because the main function requires a compositing mode of SourceCopy to work, which means if you don't draw it to another surface first anything behind the shadow will be completely replaced and the transparency aspect is useless. I'm on a roll of answering 10-year-old questions, but hopefully, this helps someone!
There is in WPF if you can stretch to using that instead, I don't believe there is an alternative in Windows Forms due to the limited capabilities of GDI+.
Here's a controversial opinion, you do it without code.
Set your main panel Border Style to Fixed Single.
Create 3 panels below it, each 1 pixel larger in every direction.
Each of the 3 panels is of a lighter shade of gray.
Not perfect but cheap and easy.
panel with pseudo-shadow

Categories