First of all I'm new to the C# world.
I just started this year learning about C# with no programming background experience.
I came here with a problem that I would like to get solved asap.
I managed to write a little code to create a list with Bitmaps including the draw positions.
What I'm trying to do is to draw every piece (picture of a dot) in the list on the form.
I've been working hours on this just trying to figure out how to get the list drawn on the form...
I placed comments behind the code to make it easier for the reader to understand whats it's for so it won't cause any brain damage or sweat to the reader. :p
See below for my code:
Form1.cs
public partial class Form1 : Form
{
private GridDrawing drawing;
private Bitmap bmpPic;
public Form1()
{
InitializeComponent();
bmpPic = new Bitmap("Dot.png"); // Picture(Dot 28*28 pixels)
this.Paint += Form_Paint;
}
private void Form_Paint(object sender, PaintEventArgs e)
{
drawing = new GridDrawing(this, bmpPic, 6, 8); // Form, Bitmap, Rows, Columns
foreach (var piece in drawing.Pieces)
{
e.Graphics.DrawImage(bmpPic, piece.Position);
}
}
private void btnStart_Click(object sender, PaintEventArgs e)
{
}
}
GridDrawing.cs
public class GridDrawing
{
private Bitmap bmpPic;
private int columns;
private int rows;
private List<GridPiece> pieces;
private Point position;
/// <summary>
/// Constructs a grid with dots.
/// </summary>
/// <param name="ctrl"></param>
/// <param name="gridPic"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
public GridDrawing(Control ctrl, Bitmap bmpPic, int rows, int columns)
{
this.bmpPic = bmpPic; //The picture(Dot).
this.rows = rows; //The amount of rows in the matrix.
this.columns = columns; //The amount of columns in the matrix.
this.pieces = new List<GridPiece>(); //Initializes the List GridPieces
Point position = new Point(0, 0); //Draw position of the picture(Dot)
Size size = new Size(bmpPic.Width, bmpPic.Height); //Size of picture(Dot).
for (int i = 0; i <= rows; i++) //A with 6 rows
{
position.X = 0; //Puts the value X on 0 when it starts a new row.
for (int j = 0; j <= columns; j++) //A matrix with 8 columns
{
GridPiece s = new GridPiece(bmpPic, position); // Creates a piece
pieces.Add(s); // Puts the piece that has to be drawn in the List<GridPiece>pieces
position.X += size.Width; // Changes the width of the draw position
}
position.Y += size.Height; // Changes the height of the draw position
}
}
public List<GridPiece> Pieces
{
get { return this.pieces; }
}
}
GridPiece.cs
public class GridPiece
{
private Bitmap bmpPic;
private Point position;
/// <summary>
/// Constructor of GriedPiece
/// </summary>
/// <param name="bmpPic"></param>
/// <param name="position"></param>
public GridPiece(Bitmap bmpPic, Point position)
{
this.bmpPic = bmpPic;
this.position = position;
}
public Point Position
{
get { return position; }
}
}
Could anyone pretty please help me solve my issue?
I updates the code several times.
You need to handle the form's Paint event, and use the methods in e.Graphics to draw your pieces in a loop.
(Probably e.Graphics.DrawImage, or perhaps FillCircle)
It would look something like
void Form_Paint(object sender, PaintEventArgs e) {
foreach(var piece in drawing.Pieces) {
e.Graphics.DrawImage(bmpPic, piece.Position);
}
}
The paint event is raised every time the forms needs to be drawn.
When the pieces move, you need to manually force the form to repaint by calling Invalidate().
Related
The goal is to extract each time a frame from the video file then make histogram from the image and then to move to the next frame. this way all the frames.
The frames extraction and the histogram manipulation is working fine when the frames have saved as images on the hard disk. but now i want to do it all in memory.
to extract the frames i'm using ffmpeg because i think it's fast enough:
ffmpeg -r 1 -i MyVid.mp4 -r 1 "$filename%03d.png
for now i'm using the ffmpeg in command prompt window.
with this command it will save on the hard disk over 65000 images(frames).
but instead saving them on the hard disk i wonder if i can make the histogram manipulation on each frame in memory instead saving all the 65000 frames to the hard disk.
then i want to find specific images using the histogram and save to the hard disk this frames.
the histogram part for now is also using files from the hard disk and not from the memory:
private void btnLoadHistogram_Click(object sender, System.EventArgs e)
{
string[] files = Directory.GetFiles(#"d:\screenshots\", "*.jpg");
for (int i = 0; i < files.Length; i++)
{
sbInfo.Text = "Loading image";
if (pbImage.Image != null)
pbImage.Image.Dispose();
pbImage.Image = Image.FromFile(files[i]);//txtFileName.Text);
Application.DoEvents();
sbInfo.Text = "Computing histogram";
long[] myValues = GetHistogram(new Bitmap(pbImage.Image));
Histogram.DrawHistogram(myValues);
sbInfo.Text = "";
}
}
public long[] GetHistogram(System.Drawing.Bitmap picture)
{
long[] myHistogram = new long[256];
for (int i=0;i<picture.Size.Width;i++)
for (int j=0;j<picture.Size.Height;j++)
{
System.Drawing.Color c = picture.GetPixel(i,j);
long Temp=0;
Temp+=c.R;
Temp+=c.G;
Temp+=c.B;
Temp = (int) Temp/3;
myHistogram[Temp]++;
}
return myHistogram;
}
and the code of the class of the constrol HistogramaDesenat :
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace Histograma
{
/// <summary>
/// Summary description for HistogramaDesenat.
/// </summary>
public class HistogramaDesenat : System.Windows.Forms.UserControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public HistogramaDesenat()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitializeComponent call
this.Paint += new PaintEventHandler(HistogramaDesenat_Paint);
this.Resize+=new EventHandler(HistogramaDesenat_Resize);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//
// HistogramaDesenat
//
this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.Name = "HistogramaDesenat";
this.Size = new System.Drawing.Size(208, 176);
}
#endregion
private void HistogramaDesenat_Paint(object sender, PaintEventArgs e)
{
if (myIsDrawing)
{
Graphics g = e.Graphics;
Pen myPen = new Pen(new SolidBrush(myColor),myXUnit);
//The width of the pen is given by the XUnit for the control.
for (int i=0;i<myValues.Length;i++)
{
//We draw each line
g.DrawLine(myPen,
new PointF(myOffset + (i*myXUnit), this.Height - myOffset),
new PointF(myOffset + (i*myXUnit), this.Height - myOffset - myValues[i] * myYUnit));
//We plot the coresponding index for the maximum value.
if (myValues[i]==myMaxValue)
{
SizeF mySize = g.MeasureString(i.ToString(),myFont);
g.DrawString(i.ToString(),myFont,new SolidBrush(myColor),
new PointF(myOffset + (i*myXUnit) - (mySize.Width/2), this.Height - myFont.Height ),
System.Drawing.StringFormat.GenericDefault);
}
}
//We draw the indexes for 0 and for the length of the array beeing plotted
g.DrawString("0",myFont, new SolidBrush(myColor),new PointF(myOffset,this.Height - myFont.Height),System.Drawing.StringFormat.GenericDefault);
g.DrawString((myValues.Length-1).ToString(),myFont,
new SolidBrush(myColor),
new PointF(myOffset + (myValues.Length * myXUnit) - g.MeasureString((myValues.Length-1).ToString(),myFont).Width,
this.Height - myFont.Height),
System.Drawing.StringFormat.GenericDefault);
//We draw a rectangle surrounding the control.
g.DrawRectangle(new System.Drawing.Pen(new SolidBrush(Color.Black),1),0,0,this.Width-1,this.Height-1);
}
}
long myMaxValue;
private long[] myValues;
private bool myIsDrawing;
private float myYUnit; //this gives the vertical unit used to scale our values
private float myXUnit; //this gives the horizontal unit used to scale our values
private int myOffset = 20; //the offset, in pixels, from the control margins.
private Color myColor = Color.Black;
private Font myFont = new Font("Tahoma",10);
[Category("Histogram Options")]
[Description ("The distance from the margins for the histogram")]
public int Offset
{
set
{
if (value>0)
myOffset= value;
}
get
{
return myOffset;
}
}
[Category("Histogram Options")]
[Description ("The color used within the control")]
public Color DisplayColor
{
set
{
myColor = value;
}
get
{
return myColor;
}
}
/// <summary>
/// We draw the histogram on the control
/// </summary>
/// <param name="myValues">The values beeing draw</param>
public void DrawHistogram(long[] Values)
{
myValues = new long[Values.Length];
Values.CopyTo(myValues,0);
myIsDrawing = true;
myMaxValue = getMaxim(myValues);
ComputeXYUnitValues();
this.Refresh();
}
/// <summary>
/// We get the highest value from the array
/// </summary>
/// <param name="Vals">The array of values in which we look</param>
/// <returns>The maximum value</returns>
private long getMaxim(long[] Vals)
{
if (myIsDrawing)
{
long max = 0;
for (int i=0;i<Vals.Length;i++)
{
if (Vals[i] > max)
max = Vals[i];
}
return max;
}
return 1;
}
private void HistogramaDesenat_Resize(object sender, EventArgs e)
{
if (myIsDrawing)
{
ComputeXYUnitValues();
}
this.Refresh();
}
private void ComputeXYUnitValues()
{
myYUnit = (float) (this.Height - (2 * myOffset)) / myMaxValue;
myXUnit = (float) (this.Width - (2 * myOffset)) / (myValues.Length-1);
}
}
}
so in the end this is what i want to do :
extract the frames from the video file in memory using the ffmpeg.
instead using Directory.GetFiles i want to make the histogram manipulation on each frame from the memory that is extracted by the ffmpeg.
each extracted frame image to use the histogram to find if there is a lightning(weather lightning) in the image.
if there is a lightning save the frame image to the hard disk.
For ffmpeg, try FFmpeg.AutoGen
But you need learn about ffmpeg api for demuxer and decoder to get raw frame.
For opencv, try emgucv (recomend)
You can try search example somewhere like this
I'm creating a program that has a predefined amount of pictureboxes inserted by the user and then added in a List. the pictureboxs have to grow and each time they would hit eachother or hit the panel boundrys it would have been game over. I can't detect the collisions between them. Problem you can find in the section with 2 foreach. Thanks in advance
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Boxen
{
public partial class FrmBoxes : Form
{
public FrmBoxes()
{
InitializeComponent();
}
//Defined Global Variables for the User input, points, the biggest box, total of points and the average of points
int UserInput;
int Points = 0, BiggestBox = 0, PointsTotal =0;
double AveragePoints = 0;
//List of points of each box and another one to insert the boxes
List<int> PointsList = new List<int>();
List<PictureBox> picboxsList = new List<PictureBox>();
//Random generator for color in the boxes
Random rnd = new Random();
/// <summary>
/// FrmBoxes_Load means when the formular will load, than Info will be shown and Timer will be set to false
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmBoxes_Load(object sender, EventArgs e)
{
// Info will be displayed
LblInfo.Text = "Click Box, as bigger as more points you get";
//Timer set to false
TmrFirst.Enabled = false;
}
/// <summary>
/// TmrFirst_Tick, each time the Timer will tick will write on 3 labels different values according with the total points, average points and size of box es
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TmrFirst_Tick(object sender, EventArgs e)
{
//this tree labels will be written from their variables values and the value that its in that variable will be connverted to string
LblPoints.Text = Convert.ToString(PointsTotal);
LblClickPoints.Text = Convert.ToString(AveragePoints);
LblBiggestBox.Text = Convert.ToString(BiggestBox);
//Loop that counts betwwen 0 and the amount of Boxes inside of the List
for (int i = 0; i < picboxsList.Count; i++)
{
//The index of the List will gain a new size of 2 pixels in height and width at each tick
picboxsList[i].Size = new Size(picboxsList[i].Width + 2, picboxsList[i].Height + 2);
}
//Verificacion if the amount of boxes in the List is inferior to the number inputed bby the user
if (picboxsList.Count < UserInput)
{
//Object Box is created with 20 Pixels height and widht and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += new EventHandler(PicBox_Click);
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
//Each created box will be added to the List
picboxsList.Add(picBox);
foreach (Control x in this.Controls)
{
foreach (Control y in this.Controls)
{
if (x != y)
{
if (x.Bounds.IntersectsWith(y.Bounds)) //&& (picBox.Bounds.IntersectsWith(x.Bounds)PnlGameField.Height)) && (picBox.Bounds.IntersectsWith(x.Bounds)PnlGameField.Width))
{
LblInfo.Text = "Game Over";
TmrFirst.Stop();
}
}
}
}
}
//Check that the list is not nothing and contains more than 1 element before continuing.
//for (outerIndex as Int32 = 0; picboxsList.Count - 2)
//for (innerIndex as Int32 = outerIndex + 1; picboxsList.Count - 1)
//if (picboxsList(outerIndex).Bounds.IntersectsWith(picboxsList(innerIndex).Bounds))
//Then slimeList(outerIndex) and slimeList(innerIndex) have collided.
}
/// <summary>
/// Pic_Box, event handler created at click, will add some values and remove after click
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_Click(object sender, EventArgs e)
{
//event handler called with click interaccion
PictureBox picBox = (PictureBox)sender;
//remove the box from the game field
picBox.Dispose();
//remove the box from the List
picboxsList.Remove(picBox);
//Width will be used to calculate the total points and add each time that is clicked//
PointsTotal += picBox.Width;
//Width will be used to calculate the points per click
Points = picBox.Width;
//Points will be added to the List
PointsList.Add(Points);
//Max value from a box will be token out from the list Points and saved in the variable BiggestBox
BiggestBox = PointsList.Max();
//AveragePoints variable will hold the double value of all the boxes points saved in the list and will update upon click in each box
AveragePoints = Convert.ToDouble(PointsList.Average());
}
/// <summary>
/// When start button will be clicked it creates the amount of boxes defined by the user
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnStart_Click(object sender, EventArgs e)
{
//new text inserted in the label LblInfo to be displayed upon click on the button start
LblInfo.Text = "When the Boxes hit eachother or the edges it's Game Over";
//User input converted to integer and displayed in text box
UserInput = Convert.ToInt32(TxtNumberOfBoxes.Text);
//Timer will be set to true
TmrFirst.Enabled = true;
//Loop to check the user input and add boxes till his given number
for (int i = 0; i < UserInput; i++)
{
//Object Box is created with 20 Pixels height and widht and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += new EventHandler(PicBox_Click);
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
//Each created box will be added to the List
picboxsList.Add(picBox);
}
}
}
}
Answer
Your collision detection does not work because you are checking the controls of the form, not of the PnlGameField. Use the following game loop instead:
private void timer1_Tick(object sender, EventArgs e)
{
var boxes = PnlGameField.Controls.OfType<PictureBox>().ToArray();
foreach (var box in boxes)
{
box.Size = new Size(box.Size.Width + 2, box.Size.Height + 2);
box.Location = new Point(box.Location.X - 1, box.Location.Y - 1);
}
if (CheckCollisions(boxes))
{
EndGame();
}
PnlGameField.Invalidate();
}
private bool CheckCollisions(PictureBox[] boxes)
{
for (int i = 0; i < boxes.Length; i++)
{
var box = boxes[i];
if (box.Left < 0 || box.Right >= PnlGameField.Width
|| box.Top < 0 || box.Bottom >= PnlGameField.Height)
{
box.BorderStyle = BorderStyle.FixedSingle;
return true;
}
for (int j = i+1; j < boxes.Length; j++)
{
var other = boxes[j];
if (box.Bounds.IntersectsWith(other.Bounds))
{
box.BorderStyle = BorderStyle.FixedSingle;
other.BorderStyle = BorderStyle.FixedSingle;
return true;
}
}
}
return false;
}
which I tested and it works 🗸.
Other interesting points.
Scoring
Create a class to keep track of scoring statistics. Convert the points into float since double is too precise (you get averages of 29.9999999997 instead of 30.0) and your conversion to double after averaging is a bug.
public class GameScore
{
readonly List<float> pointsList;
public GameScore()
{
pointsList = new List<float>();
}
public int BoxCount { get => pointsList.Count; }
public float Average { get => pointsList.Count >0 ? pointsList.Average() : 0; }
public float Maximum { get => pointsList.Count>0 ? pointsList.Max() : 0; }
public float Total { get => pointsList.Count>0 ? pointsList.Sum() : 0; }
public void Add(int points)
{
pointsList.Add(points);
}
public void Reset()
{
pointsList.Clear();
}
public override string ToString()
{
return $"Total={Total}, Ave={Average}, Max={Maximum}";
}
}
And the GameScore can be used each time a box is clicked with score.Add(box.Width). See source code below for more details
Box Creation/Removal
One other point to make is you only need to add a new box after one is clicked and removed, so there is no need to check if the current count == user input, and there is no need to duplicate code for adding boxes. Put in a function and call it as needed.
This a general observation that you will be better served if you split your code into functional units (add functions) and call them from the UI handlers. There is no need to keep a separate list of picture boxes, and you can know if the game is running or not by using the Timer.Enabled property.
Source Listing
Below is the full code listing I used for testing, and it largely based on your code, but with things re-arranged around. My hope is that you can get inspired to understand how to be better at structuring your code.
public partial class Form1 : Form
{
static Random rnd = new Random();
readonly GameScore score = new GameScore();
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
timer1.Enabled = false;
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
if (timer1.Enabled)
{
EndGame();
}
else
{
StartGame();
}
}
private void StartGame()
{
toolStripButton1.Text = "Stop";
toolStripButton1.BackColor = Color.FromArgb(255, 128, 128);
if (int.TryParse(toolStripTextBox1.Text, out int count) && count > 0)
{
PnlGameField.Controls.Clear();
for (int i = 0; i < count; i++)
{
AddRandomBox();
}
score.Reset();
toolStripTextBox2.Text = score.ToString();
timer1.Enabled = true;
}
}
private void EndGame()
{
toolStripButton1.Text = "Start";
toolStripButton1.BackColor = Color.FromArgb(128, 255, 128);
timer1.Enabled = false;
}
private void AddRandomBox()
{
//Object Box is created with 20 Pixels height and width and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += PicBox_Click;
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
}
private void PicBox_Click(object sender, EventArgs e)
{
var target = (PictureBox)sender;
if (timer1.Enabled)
{
RemoveBox(target);
}
}
private void RemoveBox(PictureBox box)
{
score.Add(box.Width);
box.Dispose();
PnlGameField.Controls.Remove(box);
AddRandomBox();
toolStripTextBox2.Text = score.ToString();
PnlGameField.Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
var boxes = PnlGameField.Controls.OfType<PictureBox>().ToArray();
foreach (var box in boxes)
{
box.Size = new Size(box.Size.Width + 2, box.Size.Height + 2);
box.Location = new Point(box.Location.X - 1, box.Location.Y - 1);
}
if (CheckCollisions(boxes))
{
EndGame();
}
PnlGameField.Invalidate();
}
private bool CheckCollisions(PictureBox[] boxes)
{
for (int i = 0; i < boxes.Length; i++)
{
var box = boxes[i];
if (box.Left < 0 || box.Right >= PnlGameField.Width
|| box.Top < 0 || box.Bottom >= PnlGameField.Height)
{
box.BorderStyle = BorderStyle.FixedSingle;
return true;
}
for (int j = i+1; j < boxes.Length; j++)
{
var other = boxes[j];
if (box.Bounds.IntersectsWith(other.Bounds))
{
box.BorderStyle = BorderStyle.FixedSingle;
other.BorderStyle = BorderStyle.FixedSingle;
return true;
}
}
}
return false;
}
}
I have made a program where you ask the number of ellipses and it makes them in a different window in c#,but I want to have a mouse over effect-which I understood is called : MouseEnter and an onclick event,which I understood is called MouseDown, but I made an array of ellipses and I tried the following :
namespace WpfApp1
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
int numOfElipses;
public Window2()
{
InitializeComponent();
numOfElipses= MainWindow.numOfElipse;
Ellipse[] ellipsePoints = new Ellipse[numOfElipses];
Random rnd = new Random();
for (int i=0;i<numOfElipses; i++)
{
SolidColorBrush brush =
new SolidColorBrush(
Color.FromRgb(
(byte)rnd.Next(255),
(byte)rnd.Next(255),
(byte)rnd.Next(255)
));
var top = rnd.Next(0, 280);
var left = rnd.Next(0, 450);
ellipsePoints[i] = new Ellipse();
ellipsePoints[i].Width = 40;
ellipsePoints[i].Height = 40;
Canvas.SetTop(ellipsePoints[i], i);
Canvas.SetLeft(ellipsePoints[i], i*45);
ellipsePoints[i].Fill = brush;
c1.Children.Add(ellipsePoints[i]);
}
}
private void E1_MouseEnter(object sender, MouseEventArgs e)
{
Random r = new Random();
Ellipse ellipsePoints = (Ellipse)sender;
ellipsePoints.Fill = new
SolidColorBrush(Color.FromRgb((byte)r.Next(255), (byte)r.Next(255),
(byte)r.Next(255)));
}
private void E1_MouseDown(object sender, MouseButtonEventArgs e)
{
c1.Children.Remove((Ellipse)sender);
}
}
}
but it doesn't work.Can anyone explain why and how do I make it change color on a mouse over(hover) randomly,and disappear/be removed on a mouse click?
I would really appreciate any help!
As mentioned in the comments, you need to actually hook up the events to the ellipses you are creating:
...
ellipsePoints[i].MouseEnter += E1_MouseEnter; // "hook up" the Mouse Enter event
ellipsePoints[i].MouseDown += E1_MouseDown; // "hook up" the Mouse Down event
c1.Children.Add(ellipsePoints[i]);
...
Simply creating the E1_MouseEnter and E1_MouseDown methods does not automatically wire them up, and that makes sense when we think about it. There could be any number of objects on the Window that have those events - how is the code supposed to know who should listen to?
I'm trying to implement a manual focus feature for my camera page so that the user can tap to focus the camera.
I'm following this StackOverflow question that's currently written in Java for native Android. I've been converting it to C# for my Xamarin.Forms Android app.
Here's what I have so far:
public class CameraPage : PageRenderer, TextureView.ISurfaceTextureListener, Android.Views.View.IOnTouchListener, IAutoFocusCallback
{
global::Android.Hardware.Camera camera;
TextureView textureView;
public void OnAutoFocus(bool success, Android.Hardware.Camera camera)
{
var parameters = camera.GetParameters();
if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeContinuousPicture)
{
parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeContinuousPicture;
if (parameters.MaxNumFocusAreas > 0)
{
parameters.FocusAreas = null;
}
camera.SetParameters(parameters);
camera.StartPreview();
}
}
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (camera != null)
{
var parameters = camera.GetParameters();
camera.CancelAutoFocus();
Rect focusRect = CalculateTapArea(e.GetX(), e.GetY(), 1f);
if (parameters.FocusMode != Android.Hardware.Camera.Parameters.FocusModeAuto)
{
parameters.FocusMode = Android.Hardware.Camera.Parameters.FocusModeAuto;
}
if (parameters.MaxNumFocusAreas > 0)
{
List<Area> mylist = new List<Area>();
mylist.Add(new Android.Hardware.Camera.Area(focusRect, 1000));
parameters.FocusAreas = mylist;
}
try
{
camera.CancelAutoFocus();
camera.SetParameters(parameters);
camera.StartPreview();
camera.AutoFocus(OnAutoFocus); //Here is the issue. How do I use the callback?
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
Console.Write(ex.StackTrace);
}
return true;
}
return false;
}
private Rect CalculateTapArea(object x, object y, float coefficient)
{
var focusAreaSize = 500;
int areaSize = Java.Lang.Float.ValueOf(focusAreaSize * coefficient).IntValue();
int left = clamp((int)x - areaSize / 2, 0, textureView.Width - areaSize);
int top = clamp((int)y - areaSize / 2, 0, textureView.Height - areaSize);
RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
Matrix.MapRect(rectF);
return new Rect((int)System.Math.Round(rectF.Left), (int)System.Math.Round(rectF.Top), (int)System.Math.Round(rectF.Right), (int)System.Math.Round(rectF.Bottom));
}
private int clamp(int x, int min, int max)
{
if (x > max)
{
return max;
}
if (x < min)
{
return min;
}
return x;
}
}
I've managed to convert most of it but I'm not sure how to properly use the AutoFocusCallback here. What should I do to call OnAutoFocus from my OnTouch event like in the java answer I linked above?
After I attached the callback, then all I need to do is subscribe the OnTouch event to my page correct or...?
For example, I tried:
textureView.Click += OnTouch; but 'no overload for 'OnTouch' matches delegate 'EventHandler'. Is there a specific event handler I need to use?
You can try change
camera.AutoFocus(OnAutoFocus);
to
camera.AutoFocus(this);
and it will be using OnAutoFocus because it implementation from IAutoFocusCallback.
And for your question about subscribe event you can try to subscribe event in OnElementChanged like this
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
try
{
this.SetOnTouchListener(this);
}
catch (Exception e)
{
}
}
And btw I don't see to use TextureView.ISurfaceTextureListener in this code.
All that happened in the linked Java answer is that they provided the code to run when the OS calls the callback:
camera.autoFocus(new Camera.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success, Camera camera) {
camera.cancelAutoFocus();
Parameters params = camera.getParameters();
if(params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE){
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
camera.setParameters(params);
}
}
});
the above does not "call" the call back, just provides the call back code to run. the OS calls the call back. So in Xamarin, you need to pass in the type that is implementing the IAutoFocusCallback interface, so You should be able to do this I would think since CameraPage is implementing the IAutoFocusCallback interface:
camera.AutoFocus(this); // "this" refers to your current CameraPage which implements the interface.
the clue here is that when you type the opening parenthesis after camera.AutoFocus the popup shows that you need to pass in a type IAutoFocusCallback, which means any type that implements that interface, so in this case that is "this" CameraPage. :-)
Since there's no complete example here, here's mine.
This solution works fine, at least for me. The camera will focus continously until a focus point is tapped. It will then focus on the tap point until you move the camera away. Then it goes back to continous focus mode.
public class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener, Android.Hardware.Camera.IPictureCallback, Android.Hardware.Camera.IShutterCallback, IAutoFocusCallback
{
// ... code removed for brevity
/// <summary>
/// Occurs whenever the user touches the screen. Here we set the focus mode to FocusModeAuto and set a focus area based on the tapped coordinates.
/// </summary>
public override bool OnTouchEvent(MotionEvent e)
{
var parameters = camera.GetParameters();
parameters.FocusMode = Camera.Parameters.FocusModeAuto;
if (parameters.MaxNumFocusAreas > 0)
{
var focusRect = CalculateTapArea(e.GetX(), e.GetY(), textureView.Width, textureView.Height, 50f);
parameters.FocusAreas = new List<Area>()
{
new Area(focusRect, 1000)
};
}
try
{
camera.CancelAutoFocus();
camera.SetParameters(parameters);
camera.AutoFocus(this);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return true;
}
/// <summary>
/// Auto focus callback. Here we reset the focus mode to FocusModeContinuousPicture and remove any focus areas
/// </summary>
public void OnAutoFocus(bool success, Camera camera)
{
var parameters = camera.GetParameters();
parameters.FocusMode = Parameters.FocusModeContinuousPicture;
if (parameters.MaxNumFocusAreas > 0)
{
parameters.FocusAreas = null;
}
camera.SetParameters(parameters);
}
/// <summary>
/// Calculates a tap area using the focus coordinates mentioned in <see href="https://developer.android.com/reference/android/hardware/Camera.Parameters.html#getFocusAreas()"/>
/// <para>
/// Coordinates of the rectangle range from -1000 to 1000. (-1000, -1000) is the upper left point. (1000, 1000) is the lower right point. The width and height of focus areas cannot be 0 or negative.</para>
/// </summary>
/// <param name="x">The X coordinate of the tapped area</param>
/// <param name="y">The Y coordinate of the tapped area</param>
/// <param name="width">The total width of the tappable area</param>
/// <param name="height">The total height of the tappable area</param>
/// <param name="focusAreaSize">The desired size (widht, height) of the created rectangle</param>
/// <returns></returns>
private Rect CalculateTapArea(float x, float y, float width, float height, float focusAreaSize)
{
var leftFloat = x * 2000 / width - 1000;
var topFloat = y * 2000 / height - 1000;
var left = RoundFocusCoordinate(leftFloat);
var top = RoundFocusCoordinate(topFloat);
var right = RoundFocusCoordinate(leftFloat + focusAreaSize);
var bottom = RoundFocusCoordinate(topFloat + focusAreaSize);
return new Rect(left, top, right, bottom);
}
/// <summary>
/// Round, convert to int, and clamp between -1000 and 1000
/// </summary>
private int RoundFocusCoordinate(float value)
{
var intValue = (int)Math.Round(value, 0, MidpointRounding.AwayFromZero);
return Math.Clamp(intValue, -1000, 1000);
}
// ... code removed for brevity
}
In the code below I am trying to zoom the image via a mouse wheel. But the code is not working properly, it is just refreshing the panel but it does not resize it. Actually I am taking the image from memory stream that is created by another class called as decrypt. Complete Image is displayed properly but I am not able to performing zooming of the image using mousewheel event.
Plz help Me.
private void Form2_Load(object sender, EventArgs e)
{
this.Width = Screen.PrimaryScreen.WorkingArea.Width;
this.Height = Screen.PrimaryScreen.WorkingArea.Height;
this.CenterToScreen();
PicturePanel= new PictureBox();
PicturePanel.Dock = DockStyle.Fill;
//PicturePanel.SizeMode = PictureBoxSizeMode.AutoSize;
//PicturePanel.SizeMode = PictureBoxSizeMode.CenterImage;
PicturePanel.Focus();
//PicturePanel.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
this.Controls.Add(PicturePanel);
View_codee v = new View_codee();
try
{
PicturePanel.Image = Image.FromStream(Decrypt.ms1);
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
this.Name = "";
}
protected override void OnMouseWheel(MouseEventArgs mea)
{
// Override OnMouseWheel event, for zooming in/out with the scroll wheel
if (PicturePanel.Image != null)
{
// If the mouse wheel is moved forward (Zoom in)
if (mea.Delta > 0)
{
// Check if the pictureBox dimensions are in range (15 is the minimum and maximum zoom level)
if ((PicturePanel.Width < (15 * this.Width)) && (PicturePanel.Height < (15 * this.Height)))
{
// Change the size of the picturebox, multiply it by the ZOOMFACTOR
PicturePanel.Width = (int)(PicturePanel.Width * 1.25);
PicturePanel.Height = (int)(PicturePanel.Height * 1.25);
// Formula to move the picturebox, to zoom in the point selected by the mouse cursor
PicturePanel.Top = (int)(mea.Y - 1.25 * (mea.Y - PicturePanel.Top));
PicturePanel.Left = (int)(mea.X - 1.25 * (mea.X - PicturePanel.Left));
}
}
else
{
// Check if the pictureBox dimensions are in range (15 is the minimum and maximum zoom level)
if ((PicturePanel.Width > (this.Width / 15)) && (PicturePanel.Height > (this.Height / 15)))
{
// Change the size of the picturebox, divide it by the ZOOMFACTOR
PicturePanel.Width = (int)(PicturePanel.Width / 1.25);
PicturePanel.Height = (int)(PicturePanel.Height / 1.25);
// Formula to move the picturebox, to zoom in the point selected by the mouse cursor
PicturePanel.Top = (int)(mea.Y - 0.80 * (mea.Y - PicturePanel.Top));
PicturePanel.Left = (int)(mea.X - 0.80 * (mea.X - PicturePanel.Left));
}
}
}
}
Source
Updated code by adding a new ImageProperty so you can set directly the Image;
public class PictureBox : System.Windows.Forms.UserControl
{
#region Members
private System.Windows.Forms.PictureBox PicBox;
private Panel OuterPanel;
private Container components = null;
private string m_sPicName = "";
#endregion
#region Constants
private double ZOOMFACTOR = 1.25; // = 25% smaller or larger
private int MINMAX = 5; // 5 times bigger or smaller than the ctrl
#endregion
#region Designer generated code
private void InitializeComponent()
{
this.PicBox = new System.Windows.Forms.PictureBox();
this.OuterPanel = new System.Windows.Forms.Panel();
this.OuterPanel.SuspendLayout();
this.SuspendLayout();
//
// PicBox
//
this.PicBox.Location = new System.Drawing.Point(0, 0);
this.PicBox.Name = "PicBox";
this.PicBox.Size = new System.Drawing.Size(150, 140);
this.PicBox.TabIndex = 3;
this.PicBox.TabStop = false;
//
// OuterPanel
//
this.OuterPanel.AutoScroll = true;
this.OuterPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.OuterPanel.Controls.Add(this.PicBox);
this.OuterPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.OuterPanel.Location = new System.Drawing.Point(0, 0);
this.OuterPanel.Name = "OuterPanel";
this.OuterPanel.Size = new System.Drawing.Size(210, 190);
this.OuterPanel.TabIndex = 4;
//
// PictureBox
//
this.Controls.Add(this.OuterPanel);
this.Name = "PictureBox";
this.Size = new System.Drawing.Size(210, 190);
this.OuterPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
#region Constructors
public PictureBox()
{
InitializeComponent();
InitCtrl(); // my special settings for the ctrl
}
#endregion
#region Properties
private Image _pictureImage;
public Image PictureImage
{
get { return _pictureImage; }
set
{
if (null != value)
{
try
{
PicBox.Image = value;
_pictureImage = value;
}
catch (OutOfMemoryException ex)
{
RedCross();
}
}
else
{
RedCross();
}
}
}
/// <summary>
/// Property to select the picture which is displayed in the picturebox. If the
/// file doesn´t exist or we receive an exception, the picturebox displays
/// a red cross.
/// </summary>
/// <value>Complete filename of the picture, including path information</value>
/// <remarks>Supported fileformat: *.gif, *.tif, *.jpg, *.bmp</remarks>
///
[Browsable(false)]
public string Picture
{
get { return m_sPicName; }
set
{
if (null != value)
{
if (System.IO.File.Exists(value))
{
try
{
PicBox.Image = Image.FromFile(value);
m_sPicName = value;
}
catch (OutOfMemoryException ex)
{
RedCross();
}
}
else
{
RedCross();
}
}
}
}
/// <summary>
/// Set the frametype of the picturbox
/// </summary>
[Browsable(false)]
public BorderStyle Border
{
get { return OuterPanel.BorderStyle; }
set { OuterPanel.BorderStyle = value; }
}
#endregion
#region Other Methods
/// <summary>
/// Special settings for the picturebox ctrl
/// </summary>
private void InitCtrl()
{
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
PicBox.Location = new Point(0, 0);
OuterPanel.Dock = DockStyle.Fill;
OuterPanel.Cursor = System.Windows.Forms.Cursors.NoMove2D;
OuterPanel.AutoScroll = true;
OuterPanel.MouseEnter += new EventHandler(PicBox_MouseEnter);
PicBox.MouseEnter += new EventHandler(PicBox_MouseEnter);
OuterPanel.MouseWheel += new MouseEventHandler(PicBox_MouseWheel);
}
/// <summary>
/// Create a simple red cross as a bitmap and display it in the picturebox
/// </summary>
private void RedCross()
{
Bitmap bmp = new Bitmap(OuterPanel.Width, OuterPanel.Height, System.Drawing.Imaging.PixelFormat.Format16bppRgb555);
Graphics gr;
gr = Graphics.FromImage(bmp);
Pen pencil = new Pen(Color.Red, 5);
gr.DrawLine(pencil, 0, 0, OuterPanel.Width, OuterPanel.Height);
gr.DrawLine(pencil, 0, OuterPanel.Height, OuterPanel.Width, 0);
PicBox.Image = bmp;
gr.Dispose();
}
#endregion
#region Zooming Methods
/// <summary>
/// Make the PictureBox dimensions larger to effect the Zoom.
/// </summary>
/// <remarks>Maximum 5 times bigger</remarks>
private void ZoomIn()
{
if ((PicBox.Width < (MINMAX * OuterPanel.Width)) &&
(PicBox.Height < (MINMAX * OuterPanel.Height)))
{
PicBox.Width = Convert.ToInt32(PicBox.Width * ZOOMFACTOR);
PicBox.Height = Convert.ToInt32(PicBox.Height * ZOOMFACTOR);
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
/// <summary>
/// Make the PictureBox dimensions smaller to effect the Zoom.
/// </summary>
/// <remarks>Minimum 5 times smaller</remarks>
private void ZoomOut()
{
if ((PicBox.Width > (OuterPanel.Width / MINMAX)) &&
(PicBox.Height > (OuterPanel.Height / MINMAX)))
{
PicBox.SizeMode = PictureBoxSizeMode.StretchImage;
PicBox.Width = Convert.ToInt32(PicBox.Width / ZOOMFACTOR);
PicBox.Height = Convert.ToInt32(PicBox.Height / ZOOMFACTOR);
}
}
#endregion
#region Mouse events
/// <summary>
/// We use the mousewheel to zoom the picture in or out
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_MouseWheel(object sender, MouseEventArgs e)
{
if (e.Delta < 0)
{
ZoomIn();
}
else
{
ZoomOut();
}
}
/// <summary>
/// Make sure that the PicBox have the focus, otherwise it doesn´t receive
/// mousewheel events !.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_MouseEnter(object sender, EventArgs e)
{
if (PicBox.Focused == false)
{
PicBox.Focus();
}
}
#endregion
#region Disposing
/// <summary>
/// Die verwendeten Ressourcen bereinigen.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
}
private void Form2_Load(object sender, EventArgs e)
{
pictureBox1.PictureImage = Image.FromStream(Decrypt.ms1);
}