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);
}
Related
i want to call a function from an external script which draws a shape.
the form currently opens up blank with no shape on it and no error.
below is empty form that calls a function from a different file
public partial class Form1 : Form
{
other_fff functions1 = new other_fff();
public Form1()
{
InitializeComponent();
functions1.draw_circle(this);
}
}
below is where i get my draw function
class other_fff
{
public void draw_circle(Form1 the_form)
{
Pen Pen1 = new Pen(Color.Blue, 9);
Graphics g = the_form.CreateGraphics();
g.DrawEllipse(Pen1, 50, 50, 10, 5); // does not work
}
}
how can i make this draw
You could add a event handler to the paint-event like this:
public static void DrawCircle(Form form)
{
form.Paint += OnPaint;
void OnPaint(object sender, PaintEventArgs e)
{
using (var Pen1 = new Pen(Color.Blue, 9))
{
e.Graphics.DrawEllipse(Pen1, 50, 50, 10, 5);
}
}
}
There are a number of downsides:
There is no possibility to remove circles unless you add some method of un registering the event.
It will be difficult to manage order of paint-calls, since it will depend on the order the eventhandlers are added.
It is a bit odd that an external part is drawing on a form. In general, forms and controls should manage their own painting if needed.
You might want to inform the form that it should draw an ellipse, and let the form do the drawing instead.
I'm trying to redraw a shape displayed on a SKCanvas the method (ChangeShape) works fine the 1st time it's called from the PaintSurface handler but when I attempt to call it again the app crashes and I receive the error "Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)".
I have tried to call the handler directly and Initialise a completely new SKSurface, but it still results in the same error.
public LinearLayout ImageDivisionLayout_Obj;
public SKCanvasView ImageViewActivity_Obj;
public SKSurface ImageCanvas;
public SKCanvas canvasSK;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_image);
ImageViewActivity_Obj = new SKCanvasView(this);
ImageViewActivity_Obj.PaintSurface += ImageViewActivity_Obj_PaintSurface;
ImageDivisionLayout_Obj = (LinearLayout)FindViewById(Resource.Id.ImageLayoutDivision);
ImageDivisionLayout_Obj.AddView(ImageViewActivity_Obj);
private void ImageViewActivity_Obj_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKImageInfo info = e.Info;
SKSurface surface = e.Surface;
ImageCanvas = surface;
SetupCanvas();
}
public void SetupCanvas()
{
ShapePaint = new SKPaint();
ShapePaint.Color = SKColor.Parse("#F00000");
ShapePaint.StrokeWidth = 25;
ShapePaint.Style = SKPaintStyle.Stroke;
ImageCanvas.Canvas.DrawBitmap(
AndroidExtensions.ToSKBitmap(ImageTaken),
new SKRect(0, 0, ImageViewActivity_Obj.Width,
ImageViewActivity_Obj.Height));
ChangeShape();
ImageCanvas.Canvas.Save();
}
public void ChangeShape()
{
float X1 = CurrentShape.X;
float Y1 = CurrentShape.Y;
float X2 = CurrentShape.X + CurrentShape.width;
float Y2 = CurrentShape.Y + CurrentShape.height;
if (CurrentShape.name == "Rectangle")
{
ImageCanvas.Canvas.DrawRect(X1, Y1, X2, Y2, ShapePaint);
}
else if (CurrentShape.name == "Circle")
{
ImageCanvas.Canvas.DrawCircle(new SKPoint(X2 - X1, Y2 - Y1), CurrentShape.width / 2, ShapePaint);
}
}
Having a look at what you are doing, I can see that this probably will not work on most platforms. The SKSurface that you are storing in the ImageCanvas field will be destroyed as soon as you leave the ImageViewActivity_Obj_PaintSurface method. The surface and canvas objects are transient and only live as long as the method is running.
I am assuming you are trying to create some sort of drawing app? If so, there are two approaches to this (although the smarter people may suggest more). You can either store all the operations in a list of some sort. Basically, if I draw a rectangle, store the operation and the bounds.
Or, you can create a background surface with SKSurface.Create(...) and then draw on that. When drawing to the screen, just copy the image from the background canvas to the screen canvas. Then, you can also just save the background canvas without trying to capture the screen canvas to save to disk.
I did a simple finger paint app here that you can check out for ideas: https://github.com/mattleibow/SkiaSharpDemo/blob/master/SkiaSharpDemo/SkiaSharpDemo/MainPage.xaml.cs
I'm not sure of how the Paint form lifecycle works, when is the Form1_Paint function called? How can I control when it's called?
I know I can call Draw a Circle using the C# Drawing library like so:
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Red, new Rectangle(1, 1, 1, 1));
}
If I define an object like so:
class myCircleObject
{
int x, y, radius;
public myCircleObject(int x_val, int y_val, int r)
{
x = x_val;
y = y_val;
radius = r;
}
public void Draw()
{
System.Drawing.Rectangle r = new System.Drawing.Rectangle(x, y, radius, radius);
//Draw Circle here
}
}
or if I can't do that how can I call the Form1_Paint function as opposed to it just running immediately at run time.
There are two ways:
The typical way is to paint asynchronously. Call Invalidate on whatever form/control has your custom drawing logic. The framework will raise the Paint event method at the appropriate time.
A more forceful (non-recommended) way is to paint synchronously. Call Refresh on your form/control, which will cause it to raise Paint immediately.
For example (this is not complete, but it illustrates the concept):
public class Form1
{
private MyCircle _circle;
private void Form1_Paint(object sender, PaintEventArgs e)
{
_circle.Draw(e); // this line causes the Circle object to draw itself on Form1's surface
}
public void MoveTheCircle(int xOffset, int yOffset)
{
_circle.X += xOffset; // make some changes that cause the circle to be rendered differently
_circle.Y += yOffset;
this.Invalidate(); // this line tells Form1 to repaint itself whenever it can
}
}
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:
I have complicated problem (maybe with simple answer).
I have class, that contains some lines, points and "Markers".
Marker is a class that contains Ellipse and its center coordinates (Point).
Marker class has drag-drop implementation, that moves ellipse and changes Marker.coordinates property. That works.
However I want to use drag-drop from my Marker class to move points in SomeShape object (Marker objects are parts of SomeShape).
I thought, that when I create Marker object and I pass 'SomeShape.lineEnds[0]' to Marker constructor - update on Marker class will update also my SomeShape.lineEnds[0], but Its not working.
How can I solve this? By using some references somehow?
I hope I described my problem clearly enough.
Code:
class SomeShape
{
// this object is set of lines and "Markers" (class below)
private List<Marker> markers;
private List<Point> lineEnds;
private List<Line> lines;
// my object can redraw itself on canvas
public RedrawMe()
{
// it removes own lines from canvas and it puts new lines
// I call this function after I add points to lineEnds collection etc.
// or when I change coordinates on one of lineEnds (list of points)
}
public void AddPoint(Point p)
{
this.lineEnds.Add(p); // adding point to line ends
this.markers.Add(new Marker(p, this, c)); // adding same point to new marker
RedrawMe();
}
}
Problematic part:
class Marker
{
public Canvas canvas;
private Ellipse e;
private Point coordinates; // ellipse center coordinates
private Object parent; // I store SomeShape object here to call RedrawMe method on it
public Marker(Point p, Object par, Canvas c)
{
this.coordinates = p;
this.canvas = c;
this.parent = par;
e = MyClassFactory.EllipseForMarker();
e.MouseDown += new System.Windows.Input.MouseButtonEventHandler(e_MouseDown);
e.MouseMove += new System.Windows.Input.MouseEventHandler(e_MouseMove);
e.MouseUp += new System.Windows.Input.MouseButtonEventHandler(e_MouseUp);
c.Children.Add(e);
e.Margin = new Thickness(p.X - (e.Width/2), p.Y - (e.Height/2), 0, 0);
}
public void MoveIt(Point nc) // nc - new coordinates
{
this.e.Margin = new Thickness(nc.X - (e.Width / 2), nc.Y - (e.Height / 2), 0, 0);
this.coordinates.X = nc.X;
this.coordinates.Y = nc.Y;
if (this.parent is SomeShape) ((SomeShape)parent).RedrawMe();
}
#region DragDrop // just drag drop implementation, skip this
private bool is_dragged = false;
void e_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e){
e.Handled = true;
is_dragged = false;
this.e.ReleaseMouseCapture();
}
void e_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
if (is_dragged)
{
this.MoveIt(e.GetPosition(canvas));
}
}
void e_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
is_dragged = true;
this.e.CaptureMouse();
}
#endregion // DragDrop
}
As for your problem, you want to "notify" the parent, although there are many ways to solve this the classic C# manner is by creating an event and firing it when needed.
On the other hand, you have the parent from the constructor. I think you should consider receiving an interface like IDrawable that will contain any methods you want to call from the parent, and through that notify the parent object that it can be redrawn.
In the drag n drop I don't see any code notifying the parent, so can't comment. but why not just activate the same call to ((SomeShape)parent).RedrawMe()?
Point is a struct, not class and it will not work like this.
I assume, because structs are not "reference types".
I created own "MyPoint" class with X and Y properties and it worked.