I am attempting to generate a set of rectangles on an image. To achieve this, I call a method that is present in a seperate class (Because multiple forms need to call this function.) Let's say I have Form A and Form B that both need to draw said set of rectangles:
From Form A it works just fine, from Form B it doesn't draw anything, but it doesn't return an exception either.
To make sure I haven't missed anything, I went as far as to copy and paste the function calls from both forms so the two of them are identical. I have also triple checked any semantic errors but have been unable to find any.
The function call from Form A is as follows:
private void PbPreview_Click(object sender, EventArgs e)
{
if (NewPage) //This bool is true when the user is displaying a new page(Image)
{
StartingY = MousePosition.Y - 76; //Save the Y position of the click in a float variable
Form1.MainController.DrawRect(StartingY, PbPreview.Image); //Function call
NewPage = false; //Set the new Page bool to false to prevent overdrawing
}
}
The function call from Form B is as follows:
private void PbFactuur_Click(object sender, EventArgs e)
{
if (NewPage) //Same use as the NewPage bool from above
{
MouseY = MousePosition.Y - 76; //Saving mouse position
Form1.MainController.DrawRect(MouseY, PbFactuur.Image); //Function call
NewPage = false; //Set new page to false to prevent overdrawing
MessageBox.Show("I have executed the function"); //Debug info
}
}
And here is the code that is present within the function:
public void DrawRect(float Ypos, Image DrawSubject)
{
try
{
foreach (Rectangle R in Form1.nieuwBedrijf.Rects)
{
Rectangle TempRect = R;
TempRect.Y = Convert.ToInt32(Ypos);
Graphics G = Graphics.FromImage(DrawSubject);
G.DrawRectangle(Pens.Black, TempRect.X * Form1.nieuwBedrijf.ScaleX, TempRect.Y * Form1.nieuwBedrijf.ScaleY, TempRect.Width * Form1.nieuwBedrijf.ScaleX, 1920);
}
}
catch
{
MessageBox.Show("No rectangles have been defined yet.");
}
}
Sidenote: Rects is a list of user defined rectangles.
The expected result would be that at the location where the user clicks, the set of rectangles will appear. But in reality, nothing appears at all.
The application doesn't return any sort of error message, and with the use of breakpoints and messageboxes I have been able to verify that the function does execute.
I hope anyone is able to point me to a potential solution to this problem.
Many thanks in advance
~Melvin
After some tinkering around I have found the following solution:
The rectangles were actually being drawn, but it was not being shown on screen. To have the rectangles show up on screen, I had to add the following line of code:
private void PbFactuur_Click(object sender, EventArgs e)
{
if (NewPage) //Same use as the NewPage bool from above
{
MouseY = MousePosition.Y - 76; //Saving mouse position
Form1.MainController.DrawRect(MouseY, PbFactuur.Image); //Function call
NewPage = false; //Set new page to false to prevent overdrawing
MessageBox.Show("I have executed the function"); //Debug info
Refresh();//<----- This line fixed the problem
}
}
Related
Original Question: Edit properties of Graphics object c#
I've managed to create the object models and converted the entire program to use this structure so many thanks for the template there. My only problem is, I need to use threading to implement the color-changing feature. I have implemented the following code but have no luck:
private void pictureBox6_Click(object sender, EventArgs e)
{
running = !running;
}
public void redgreenMethod()
{
while (true)
{
if (running != true) continue;
if (flag == false)
{
flag = true;
Shape x = shapes[methodCounter - 1];
x.pen = Red;
}
else
{
flag = false;
Shape x = shapes[methodCounter];
x.pen = Green;
}
Thread.Sleep(500);
}
}
Surely the code inside the "if" statement changes the pen for the most recently added shape? I've entered a breakpoint and it shows that the pen does change constantly.. however, I don't see this being implemented on the Canvas?!
Where am I going wrong?!
From my previous comment:
Most likely redgreenMethod() is running in the main UI thread and the
tight loop is preventing the controls from updating themselves. Use a
TIMER instead. Set its Interval property to 500, and handle the Tick()
event. You don't need the while loop then. Instead of running you just
toggle the Enabled state of the Timer.
Here's what that might look like in code:
private void pictureBox6_Click(object sender, EventArgs e)
{
timer1.Enabled = !timer1.Enabled;
}
private void timer1_Tick(object sender, EventArgs e)
{
flag = !flag;
shapes[methodCounter - 1].pen = flag ? Green : Red;
}
You made these comments in an answer to your previous question:
Once a line has been drawn via the graphics: "g.DrawLine", the
properties of this line are then inaccessible. I need to find out how
to actually access these properties so I can use them inside the
Thread method.
In short, you DON'T access the properties of the line that has already been drawn. What you would do is update the color in the Shape (which you're already doing), and then call Invalidate() against the control that needs to be redrawn, such as pictureBox6.Invalidate(). This will force the Paint() event to fire again where everything will then be re-drawn, presumably with the new color that was set in the Shape properties.
I don't know if I still have missing on this but whenever you want to add PictureBox, you just find it on Toolbox and drag it on the UI. But what I did is I coded it by adding PictureBox then what I want is I have to move it to the right. I coded it on a Timer and this is how I do.
private void enemyMove_Tick(object sender, EventArgs e)
{
GameMenu menu = new GameMenu();
if (menu.cmbox_Level.Text.Equals("Easy"))
{
this.Controls.Add(menu.EnemyTank);
menu.EnemyTank.Width = 26;
menu.EnemyTank.Height = 32;
menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
menu.EnemyTank.Left += 2;
}
}
Don't ask me if the Timer is started. Yes it already started, it was coded on the Form. But somehow when I start the program it doesn't move. But then I tried adding PictureBox, this time I tried dragging it to UI then add an Image on it, then coded it by moving to the right. When I start the program it works. But what I want is that whenever I start the button it will just make the random PictureBox move to the right. Idk what's the missing in here.
I made some assumptions. If I my assumptions aren't correct, please update your post for details. Following content is copied from my comment.
A new GameMenu will always create a new EnemyTank.
A EnemyTank's default Left value is fixed;
Your enemyMove_Tick is keep creating GameMenu and EnemyTank.
That is, every tick, a EnemyTank is created and value changed to "default value plus 2" in your code.
To avoid it, you have to keep EnemyTank instance somewhere instead of keep creating it.
Here's a example.
GameMenu m_menu;
void YourConstructor()
{
// InitializeComponent();
// something else in your form constructor
m_menu = new GameMenu();
m_menu.EnemyTank.Width = 26;
m_menu.EnemyTank.Height = 32;
m_menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
m_menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
this.Controls.Add( m_menu.EnemyTank );
}
private void enemyMove_Tick(object sender, EventArgs e)
{
if (menu.cmbox_Level.Text.Equals("Easy"))
{
m_menu.EnemyTank.Left += 2;
}
}
For my programming class we are required to make a memory match game. When the player clicks on a panel an image is displayed. The player clicks on two panels, if the images match they are removed, if they are different they are turned face down.
The problem I am having with this is that only the image from the first panel gets displayed, even though I am using the same line of code to display the images from both panels, and use Thread.Sleep to pause the program after the second tile has been picked. I don't understand why this is happening, any help would be appreciated.
private void tile_Click(object sender, EventArgs e)
{
string tileName = (sender as Panel).Name;
tileNum = Convert.ToInt16(tileName.Substring(5)) - 1;
//figure out if tile is locked
if (panelArray[tileNum].isLocked == false)
{
pickNum++;
//although the following line of code is used to display the picture that is stored in the tile array
//what is happening is that it will only display the picture of the first tile that has been picked.
//when a second tile is picked my program seems to ignore this line completely, any ideas?
panelArray[tileNum].thisPanel.BackgroundImage = tiles[tileNum].tileImage;
if (pickNum == 1)
{
pick1 = tileNum;
panelArray[tileNum].isLocked = true;
}
else
{
pick2 = tileNum;
UpdateGameState();
}
}
}
private void UpdateGameState()
{
Thread.Sleep(1500);
if (tiles[pick1].tag == tiles[pick2].tag)//compares tags to see if they match.
{
RemoveTiles();
}
else
{
ResetTiles();
}
pickNum = 0;
guess += 1;
guessDisplay.Text = Convert.ToString(guess);
if (correct == 8)
{
CalculateScore();
}
}
try this:
(sender as Panel).BackgroundImage = tiles[tileNum].tileImage;
you have to be sure that your tile_Click method is associated to both panel...
I am trying to adapt Command Pattern to simple paint application with undo functionality. And I've stuck with OnPaint Event on Undo operations. This is the code:
[SOLVED] details at the end of the post
interface ICommand {
void Execute();
void UnExecute();
}
class DrawLineCommand : ICommand {
private SimpleImage simpleImage;
private Image prevImage;
public DrawLineCommand(SimpleImage simpleImage) {
this.simpleImage = simpleImage;
this.prevImage = simpleImage.Image;
}
public void Execute() {
simpleImage.DrawLine();
}
public void UnExecute() {
simpleImage.Image = prevImage;
}
}
class CommandManager {
private Stack undoStack = new Stack();
public void ExecuteCommand(ICommand command) {
command.Execute();
undoStack.Push(command);
}
public void UnExecuteCommand() {
if (undoStack.Count > 0) {
ICommand command = (ICommand)undoStack.Pop();
command.UnExecute();
}
}
}
class SimpleImage {
private Point startPoint;
private Point endPoint;
private PictureBox pictureBox;
public SimpleImage(PictureBox pictureBox) {
this.pictureBox = pictureBox;
pictureBox.Paint += new PaintEventHandler(pictureBox_Paint);
}
void pictureBox_Paint(object sender, PaintEventArgs e) {
// this code shows the line during drawing
// this code is under "if operation == drawLine" block
Graphics graphics = e.Graphics;
graphics.DrawLine(Pens.Red, startPoint, endPoint);
// how can i refresh picturebox after undo operation?
// "if operation == undo" then ??
}
public void DrawLine() {
// this code actually saves finally drawn line
Image img = Image;
Graphics graphics = Graphics.FromImage(img);
graphics.DrawLine(Pens.Red, startPoint, endPoint);
Image = img;
}
public void Invalidate() {
pictureBox.Invalidate();
}
public Image Image {
get { return pictureBox.Image; }
set { pictureBox.Image = value; }
}
public Point StartPoint {
get { return startPoint; }
set { startPoint = value; }
}
public Point EndPoint {
get { return endPoint; }
set { endPoint = value; }
}
}
public partial class FormMain : Form {
private PictureBox pictureBox;
private SimpleImage simpleImage;
private CommandManager commandManager;
public FormMain() {
InitializeComponent();
simpleImage = new SimpleImage(this.pictureBox);
commandManager = new CommandManager();
}
void pictureBox_MouseDown(object sender, MouseEventArgs e) {
if (e.Button != MouseButtons.Left)
return;
simpleImage.StartPoint = e.Location;
}
void pictureBox_MouseMove(object sender, MouseEventArgs e) {
if (e.Button != MouseButtons.Left)
return;
simpleImage.EndPoint = e.Location;
simpleImage.Invalidate();
}
void pictureBox_MouseUp(object sender, MouseEventArgs e) {
simpleImage.Invalidate();
commandManager.ExecuteCommand(new DrawLineCommand(simpleImage));
}
}
It actually draws a line, it executes command and push it on the stack. I cannot achieve working UNDO. I mean. Step-by-step debugging I see the object pops from the stack, and then OnPaint executes. But no 'previous' image is actually shown.
I have read many sites, and I have also sample app from one of codeproject site's / articles. It presents the same approach with TextBox and Bold / Italicize operations. It works like hell. The only difference is this cruel OnPaint method..
Thanks in advance for any advices!
[EDIT] in a rush i forgot that assigning one reference type to another is not copying it (creating independent object), changing this:
this.prevImage = simpleImage.Image;
in few places solved the problem. Everything works now..
The point here would be not to paint directly on the canvas, but rather have a data structure that represents your painting. You would then add a line to this painting object, and the main loop of the canvas would draw the appropriate graphic from the data structure. Then your do/undo methods would simply need to manipulate the data structure, not do the painting.
You would need something like this:
interface IPaintable // intarface for Lines, Text, Circles, ...
{
void OnPaint(Image i); // does the painting
}
interface IPaintableCommand // interface for commands
{
void Do(ICollection<IPaintable> painting); // adds line/text/circle to painting
void Undo(ICollection<IPaintable> painting); // removes line/text/circle from painting
}
Your main application would simply keep a List, and repaint the canvas when a command changes the painting collection.
It looks like you have an aliasing problem. In your DrawLineCommand you pull in a reference to the image before the operation and store it as such:
this.prevImage = simpleImage.Image;
You now have two references to the same object. The draw line operation happens you operate on that same image:
Image img = Image; // Now a third reference to the same image object
Graphics graphics = Graphics.FromImage(img);
graphics.DrawLine(Pens.Red, startPoint, endPoint);
Image = img; // and you set the Image reference back to the same object
The above makes img an unnecessary reference to Image. But, you still have another reference to Image in your command. After the garbage collector runs, you're back down to to references to the same image object. Undo then executes the following:
simpleImage.Image = prevImage;
Here you haven't changed Image, only made Image reference the same object it was alreafy referencing.
Although I strongly agree with m0sa, the fix, in this case, is to make prevImage a COPY of the original image at the time you create your command. For the following I assume that Image.Clone() is implemented, though I've never tried it myself:
this.prevImage = simpleImage.Image.Clone();
NOTE: you may quickly run out of memory with large images or many commands if you use this approach.
I did quite a bit of searching around and didn't find anything of much help.
Is it possible to "slide" or "move" using C#, an object from one Location to another using a simple For loop?
Thank you
I would suggest you rather use a Timer. There are other options, but this will be the simplist if you want to avoid threading issues etc.
Using a straight for loop will require that you pump the message queue using Application.DoEvents() to ensure that windows has the opportunity to actually render the updated control otherwise the for loop would run to completion without updating the UI and the control will appear to jump from the source location to the target location.
Here is a QAD sample for animating a button in the Y direction when clicked. This code assumes you put a timer control on the form called animationTimer.
private void button1_Click(object sender, EventArgs e)
{
if (!animationTimer.Enabled)
{
animationTimer.Interval = 10;
animationTimer.Start();
}
}
private int _animateDirection = 1;
private void animationTimer_Tick(object sender, EventArgs e)
{
button1.Location = new Point(button1.Location.X, button1.Location.Y + _animateDirection);
if (button1.Location.Y == 0 || button1.Location.Y == 100)
{
animationTimer.Stop();
_animateDirection *= -1; // reverse the direction
}
}
Assuming that the object you're talking about is some kind of Control you could just change the Location property of it.
So something like this:
for(int i = 0; i < 100; i++)
{
ctrl.Location.X += i;
}
Should work I think.