Command Pattern and OnPaint Event issue - c#

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.

Related

Edit Values of Shapes in List C#

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.

Drawing Rectangles on an image from a different class

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
}
}

Cocoa: Autosave window not working

I'm trying to save and restore the window bounds using Cocoa with C#. I tried the following according to docs, but it does not work. What I'm missing?
public class MyWindow: NSWindow
{
internal MyWindow() : base()
{
SetSavedFrame();
//Attach window will close event to MyWindow_WillClose
[...]
}
[...]
void SetSavedFrame()
{
FrameAutosaveName = AUTOSAVE_NAME;
if (SetFrameUsingName(FrameAutosaveName, true))
return;
SetFrame(new RectangleF (0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT), true);
Center();
}
void MyWindow_WillClose(object sender, EventArgs args)
{
SaveFrameUsingName(FrameAutosaveName);
}
}
If I cat the content of my .plist file, it is always the same, so I think the problem is, at least, when saving the preferences.
Thank you.

how to get the height and width of an image in C#

I am trying to get the height and width of an image from websites but it always return 0, as the image is not yet downloaded so i used the following code and still didnt work as the image will start downloading only after the method end, so it hang
someMethod
{
foreach(string imagepath in paths){
IsDownloaded = false;
image = new BitmapImage(new Uri(imagepath));
image.ImageOpened += image_ImageOpened;
while (!IsDownloaded) ;
/// code that will use image.PixelHeight only if it satisfy a condition then break
}
private void image_ImageOpened(object sender, RoutedEventArgs e)
{
IsDownloaded = true;
}
Does anyone have any alternative or any fix for this supported in metro style apps
You can't use asynchronous programming like that - remove this line:
while (!IsDownloaded) ;
And put everything after it inside the image_ImageOpened method.
We usually refer to this as 'chaining', when you have a bunch of asynchronous methods, you have to continue processing after the completion of each one.
An example from my own code of getting width/height:
BitmapImage imageSource = new BitmapImage();
private void getImage()
{
Uri uir= new Uri("PATH", UriKind.Absolute);
imageSource.ImageOpened += new EventHandler<RoutedEventArgs>(imageopenened);
}
void imageopened(object sender, RoutedEventArgs e)
{
HEIGHT = ImageSource.PixelHeight;
WIDTH = ImageSource.PixelWidth;
...
}

PictureBox to Bitmap or Image?

I am trying to change the code http://sites.google.com/site/webcamlibrarydotnet/winfrom-and-csharp-sample-code-and-download from picture box to image or bitmap as I dont want to display any image or plan to display, all I want is that it will output the image to file.
I have tried changing the webcam.cs from PictureBox _FrameImage to Bitmap _FrameImage and PictureBox ImageControl to Bitmap ImageControl and casting (Bitmap) at e.WebCamImage
As well as changing it on the main form:
private void bWebcam_Click(object sender, EventArgs e)
{
WebCam webcam = new WebCam();
Bitmap image = null;
webcam.InitializeWebCam(ref image);
webcam.Start();
webcam.Stop();
FileStream fstream = new FileStream("testWebcam.jpg", FileMode.Create);
image.Save(fstream, System.Drawing.Imaging.ImageFormat.Jpeg);
fstream.Close();
}
Unhappyly it doesnt seem to work so
how could I change it from picture
box to Bitmap or Image or similar
storage before saving it to a file or
save it to file directly ?
The source code I am using is:
http://sites.google.com/site/webcamlibrarydotnet/winfrom-and-csharp-sample-code-and-download
Instead of using the WebCam class, why not just use the WebCamCapture class directly (since you are not displaying this in a form) and handle the ImageCapture event directly. The event argument for the event contains the Image. You could, in the event handler save the image to disk. Alternately, if you want to use the sample and the WebCam class, and you have a form. Use a PictureBox but leave it hidden (set Visible to false) and then just copy the image from there and save to disk when you need to.
Here is some sample code of using the WebCamCapture class instead of the WebCam class. It should be noted that this code is based on the sample code from the link provided in the question. I have kept the style of the sample so that code lines up.
Edit: Adding example of using WebCamCapture instead of WebCam class. This code should be used to modify Form1.cs in the sample code.
// Instead of having WebCam as member variable, have WemCamCapture
WebCamCapture webCam;
// Change the mainWinForm_Load function
private void mainWinForm_Load(object sender, EventArgs e)
{
webCam = new WebCamCapture();
webCam.FrameNumber = ((ulong)(0ul));
webCam.TimeToCapture_milliseconds = 30;
webCam.ImageCaptured += webcam_ImageCaptured;
}
// Add the webcam Image Captured handler to the main form
private void webcam_ImageCaptured(object source, WebcamEventArgs e)
{
Image imageCaptured = e.WebCamImage;
// You can now stop the camera if you only want 1 image
// webCam.Stop();
// Add code here to save image to disk
}
// Adjust the code in bntStart_Click
// (yes I know there is a type there, but to make code lineup I am not fixing it)
private void bntStart_Click(object sender, Event Args e)
{
webCam.Start(0);
}
Using reflector you can see that internally the WebCam class uses a timer to simulate a framerate. Therefore calling start and stop right after each other will never generate an image since the application doesnt handle application events (and therefore the timer tick event) in between starting and stopping. You should register on the ImageChanged event and call stop in there.
Good luck
** Edit: the start logic **
public void Start(ulong FrameNum)
{
try
{
this.Stop();
this.mCapHwnd = capCreateCaptureWindowA("WebCap", 0, 0, 0, this.m_Width, this.m_Height, base.Handle.ToInt32(), 0);
Application.DoEvents();
SendMessage(this.mCapHwnd, 0x40a, 0, 0);
SendMessage(this.mCapHwnd, 0x432, 0, 0);
this.m_FrameNumber = FrameNum;
this.timer1.Interval = this.m_TimeToCapture_milliseconds;
this.bStopped = false;
this.timer1.Start();
}
catch (Exception exception)
{
MessageBox.Show("An error ocurred while starting the video capture. Check that your webcamera is connected properly and turned on.\r\n\n" + exception.Message);
this.Stop();
}
}
In WebCam.cs you have:
public void InitializeWebCam(ref System.Windows.Forms.PictureBox ImageControl)
{
webcam = new WebCamCapture();
webcam.FrameNumber = ((ulong)(0ul));
webcam.TimeToCapture_milliseconds = FrameNumber;
webcam.ImageCaptured += new WebCamCapture.WebCamEventHandler(webcam_ImageCaptured);
_FrameImage = ImageControl;
}
void webcam_ImageCaptured(object source, WebcamEventArgs e)
{
_FrameImage.Image = e.WebCamImage;
}
If you modify the ImageCaptured code you can do what you want: e.WebCamImage is an Image.
for example you could change/add constructor to accept a file name and, in the ImageCaptured event, you could save image to file.

Categories

Resources