Drawing things on a Canvas - c#

How would I draw something on a Canvas in C# for Windows Phone?
Okay, let me be a little more clear.
Say the user taps his finger down at 386,43 on the canvas. (the canvas is 768 by 480)
I would like my application to be able to respond by placing a red dot at 386,43 on the canvas.
I have no prior experience with Canvas whatsoever.
If this is too complex to be answered in one question (which it probably is), please give me links to other websites with Canvas and Drawing articles.

There are various ways of doing this. Depending on the nature of the red dot, you could make it a UserControl. For a basic circle, you can simply handle your canvas' ManipulationStarted event.
private void myCanvas_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Fill = new SolidColorBrush(Colors.Red);
Canvas.SetLeft(el, e.ManipulationOrigin.X);
Canvas.SetTop(el, e.ManipulationOrigin.Y);
myCanvas.Children.Add(el);
}

I think you need to approach the problem differently. (I'm not including code on purpose, because of that).
Forms and controls in an Windows applications (including Phone) can be refreshed for several reasons, at any time. If you draw on a canvas in response to a touch action, you have an updated canvas until the next refresh. If a refresh occurs the canvas repaints itself, you end up with a blank canvas.
I have no idea what your end goal is, but you likely want to either keep track of what the user has done and store that state somewhere and show it in a canvas on the repaint of the canvas. This could be done with storing all the actions and "replaying" them on the canvas, or simply storing the view of the canvas as a bitmap and reload the canvas with that bitmap when refreshed. But, in the later case I think using a canvas isn't the right solution.

Related

How can I make the transparency from a PictureBox work properly when it's over another PictureBox?

I was doing a game project in Windows Forms and really loved how it turned out, except for one thing that was bugging me: the new picturebox's I am adding are "eating" away from the one behind it, showind the background of its parent and not showing the image behind him, as I thought it will. Apparently that's how transparency works in Windows Forms, it copies the colors behind him, basically.
This is how it looks, and I want the animals to be seen fully.
I also tried this from another post here, but it turned out like this.
There might be no solution to this, I have other things in this little game I made. There is another picturebox with other buttons and stuff, that represents the shop. And also you can see in both images that there is a pannel in the bottom section with some details. In that case, I would leave it as it is and maybe try another time to move it to WPF.
=================== EDIT ===================
The accepted answer helped me switch from a game with overlaying PictureBoxes to a game where I "paint" each frame of the game on the background. Check that answer's comment for more details about this :) This is how it turned out.
This is specifically for my code, where I have a static Resources class. Yours could look a lot cleaner, maybe you have this Render function where you have every other rectangle and image. I hope this helps everyone that visits this page :)
// ================ SOLUTION ================
public static void Render()
{
//draw the background again. This is efficient enough, maybe because the pixels that did not changed won't be redrawn
grp.DrawImage(Resources.gameBackground, 0, 0);
//draw the squirrel image on the position and length of the "squirrel" Rectangle
grp.DrawImage(Resources.currentSquirrelImage, Resources.squirrel.X, Resources.squirrel.Y, Resources.squirrel.Width, Resources.squirrel.Height);
//after that, draw each projectile (acorns, wallnuts) the same way
foreach (Projectile projectile in Resources.projectiles)
{
grp.DrawImage(projectile.image, projectile.rect.X, projectile.rect.Y, projectile.rect.Width, projectile.rect.Height);
}
//then draw each animal
foreach (Enemy animal in Resources.enemies)
{
grp.DrawImage(animal.image, animal.rect.X, animal.rect.Y, animal.rect.Width, animal.rect.Height);
}
//and finally, the image that shows where the squirrel is shooting
grp.DrawImage(Resources.selectionImge, Resources.selection.X, Resources.selection.Y, Resources.Selection.Width, Resources.Selection.Height);
//update the image of the game picturebox
form.TheGame.Image = bmp;
}
As you have noticed .net control transparency is not real transparency, it copies it's parent background, so if you have other sibiling controls the one with the higher Z index will occlude the others.
If you want to create a game avoid the usage of picture boxes, there are many options: use a game engine like Unity or roll your own.
Something easy to do is to create a Bitmap, render your game in it and then present it in your form, but beware, that can be slow.
EDIT: As you requested here is an example on how to use the Intersect function of the Rectangle struct to determine which parts of two rectangles overlap.
Rectangle R1 = new Rectangle (0,0,32,32);
Rectangle R2 = new Rectangle (16,16,32,32);
//To test if a rectangle intersects with another...
bool intersects = R1.IntersectsWith(R2); //If does not intersect then there's nothing to update
//To determine the area that two rectangles intersect
Rectangle intersection = Rectangle.Intersect(R1, R2); //In this example that would return a rectangle with (16,16,16,16).

WPF Touch - Choosing Manipulation Container based on number of manipulators

I have a WPF Touch app (MS Surface, VS 2017, .NET 4.7) that shows a number of simple shapes (lines, rects, circles) above an image, all on on a canvas. I'm having a trouble with selecting my ManipulationContainer for touch.
Roughly what I have is this:
<ScrollViewer x:Name="MyViewer"
ManipulationStarting="Viewer_ManipulationStarting">
<Canvas x:Name="MyCanvas">
<Image x:Name="MyImage" Source={Binding MyImageSource}"/>
<ItemsControl x:Name="MyShapes" ItemsSource="{Binding MyShapes}"/>
</Canvas>
</ScrollViewer>
I want let my user do two different things with touch.
With one finger they can draw new shapes (relative to the canvas)
With two fingers they can zoom the entire canvas (relative to the scrollviewer)
So if the user is drawing a new shape, then ManipulationContainer must be "MyCanvas". But if the user is zooming the whole scene, then the ManipulationContainer must be "MyViewer" (because in that case I'm changing the LayoutTransform of the whole canvas).
So I need (it would seem) to select from one of two different manipulation containers depending on which of these operations is happening. But I can't seem to figure out how. Below is what I have (which doesn't work) where I choose the container. in the ManipulationStarting handler.
private void Scene_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
// If zooming, container is the parent viewer. Otherwise it's
// drawing a shape relative to the canvas.
if (e.Manipulators.Count() >= 2)
e.ManipulationContainer = MyViewer; // Zooming whole canvas
else
e.ManipulationContainer = MyCanvas; // Drawing on canvas
e.Handled = true;
}
As you likely guessed, when I first get this, I always get just one manipulator; The user is extremely unlikely to have his very first touch be with two fingers exactly simultaneously. So my code always thinks I'm drawing a shape.
It's only later on, in the ManipulationDelta that I start getting more than one manipulator. But it's too late then to choose a container. It's already been chosen. And if I check the number of manipulators then, my coordinates, delta, and origin are all relative to the very thing I'm trying to move. It makes the zooming jump all over the place.
I don't know what to do about this. I have to let my user zoom at any time so I do not have the option of forcing them to choose a "zoom tool".
I'm sure I'm missing something simple but I don't know what it is and I'd like to avoid blind alleys. Any suggestions?

Photoshop like background on transparent image

I'm making a graphics editor for my class project and i want to make so that when, for example a user loads a picture in to the editor or or draw something in the PictureBox, all the alpha parts are shown the chessboard like background.
My idea is that when I create a PictureBox with transparent background set, I create another one behind it, set its BackColor to white and add grey images 50x50, alternately horizontally and vertically. Is that a good approach to the problem? If, not do You have any suggestions?
In Photoshop, for example, I create image 1600x1600. When I zoom to a certain level, it shrinks the boxes and adds more of them to fill the image. If You'we used Photoshop of similar program you know what I mean. Now, how would I go about achieving the same effect?
Creating a Photoshop-like program is a fun project.
There will be many challenges along your way and it is well worth thinking ahead a little..
Here is a short and incomplete list of things to keep in mind:
Draw- and paint actions
Undo, redo, edit
Multiple layers
Zooming and scrolling
Saving and printing
So getting a checkerboard background is only the start of a long journey..
Using a PictureBox as the base canvas is a very good choice, as its several layers will help. Here is a piece of code that will provide you with a flexible checkerboard background, that will keep its size even when you scale the real graphics:
void setBackGround(PictureBox pb, int size, Color col)
{
if (size == 0 && pb.BackgroundImage != null)
{
pb.BackgroundImage.Dispose();
pb.BackgroundImage = null;
return;
}
Bitmap bmp = new Bitmap(size * 2, size * 2);
using (SolidBrush brush = new SolidBrush(col))
using (Graphics G = Graphics.FromImage(bmp) )
{
G.FillRectangle(brush, 0,0,size, size);
G.FillRectangle(brush, size,size, size, size);
}
pb.BackgroundImage = bmp;
pb.BackgroundImageLayout = ImageLayout.Tile;
}
Load an Image for testing and this is what you'll get, left normal, right zoomed in:
Yes, for saving this background should be removed; as you can see in the code, passing in a size = 0 will do that.
What next? Let me give you a few hints on how to approach the various tasks from above:
Scrolling: Picturebox can't scroll. Instead place it in a Panel with AutoScroll = true and make it as large as needed.
Zooming: Playing with its Size and the SizeMode will let you zoom in and out the Image without problems. The BackgroundImage will stay unscaled, just as it does in Photoshop. You will have to add some more code however to zoom in on the graphics you draw on top of the PB or on the layers. The key here is scaling the Graphics object using a Graphics.MultiplyTransform(Matrix).
Layers: Layers are imo the single most useful feature in PhotoShop (and other quality programs). They can be achieved by nesting transparent drawing canvases. Panels can be used, I prefer Labels. If each is sitting inside the one below it and the one at the bottom has the PB as its Parent, all their contents will be shown combined.
Don't use the Label directly but create a subclass to hold additional data and know-how!
Changing their order is not very hard, just keep the nested structure in mind and intact!
Hiding a layer is done by setting a flag and checking that flag in the painting actions
Other data can include a Name, Opacity, maybe an overlay color..
The layers should also be shown in a Layers Palette, best by creating a thumbnail and inserting a layer userobject in a FlowLayoutPanel
Draw Actions: These are always the key to any drawing in WinForms. When using the mouse to draw, each such activity creates an object of a DrawAction class you need to design, which holds all info needed to do the actual drawing, like:
Type (Rectangle, filledRectangle, Line, FreeHandLine (a series of Points), Text, etc.etc..)
Colors
Points
Widths
Text
The layer to draw on
maybe even a rotation
Along with the LayerCanvas class the DrawAction class will be the most important class in the project, so getting its design right is worth some work!
Only the topmost layer will receive the mouse events. So you need to keep track which layer is the active one and add the action to its actions list. Of course the active layer must also be indicated in the Layers Palette.
Since all drawing is stored in List(s), implementing a unlimited undo and redo is simple. To allow for effective drawing and undo, maybe a common action list and an individual list for each layer is the best design..
Undo and Redo are just matter of removing the last list element and pushing it onto a redo-stack.
Editing actions is also possible, including changing the parameters, moving them up or down the actions list or removing one from the middle of the list. It help to show an Actions Palette, like F9 in PhotoShop.
To flatten two or more layers together all you need is to combine their action lists.
To flatten all layers into the Image you only need to draw them not onto their canvas but into the Image. For the difference of drawing onto a control or into a Bitmap see here! Here we have the PictureBox.Image as the second level of a PB's structure above the Background.Image. (The 3rd is the Control surface, but with the multiple layers on top we don't really need it..)
Saving can be done by either by Image.Save() after flattening all Layers or after you have switched off the BackgroundImage by telling the PB to draw itself into a Bitmap ( control.DrawToBitmap() ) which you can then save.
Have fun!

Clear drawn graphics on a picturebox without clearing original picture

In a geographical software written in C#, a PictureBox is used to show GIS map that is saved as a png file in a temporary directory. There is some geometric shapes we need to be drawn on map. We used System.Drawing methods to perform this action.
Sometimes we need to change some properties these shapes or delete them, we need to remove the shapes without making beneath them black. Drawing them again with Color.Transparent obviously doesn't work, using Graphics#Clear(Color.Transparent) doesn't work too for the same reason.
We even tried using another picture box with transparent background that is used only for purpose of drawing shapes on; so that when we use Graphics#Clear(Color.Transparent) map container remains untouched. Sounded like a perfect idea at first, but because i don't know how and why it makes map container PictureBox invisible and map viewer panel is totally black, this idea failed too.
MapViewerForm
|-- Toolbar
|-- StatusBar
|-- MapViewer Panel (Provides scrollbars)
|-- MapContainer Pictutebox
|-- Shapes drawing canvas PictureBbox (The same size and location as map container, only difference is z-order)
I prefer to use the two PictureBoxes and making 'layers' idea, i think it's less unprofessional than the other idea (I am actually a java developer and this is a C# project!), I think there should be something like java's JLayeredPane in C# to adjust z-order of those two picture boxes and omit black screen bug, But if there is a solution to draw shapes on map container itself and then clear them without losing portions of maps lying behind them i'd appreciate that answer too.
P.S: If we load map picture from file and store it in a Bitmap or Image private field and when we need to clear drawings, load image from that field with a piece of code like picMapArea.Image = MapViewer.getInstance().getMapImage(); (Note: MapViewer is a singleton class) the painted shapes will be gone but it's obviously not anything like a "good idea" because of poor performance and lagging.
Thanks in advance.
Simply draw the shapes in an event handler for the picturebox Paint event.
To restore the view, all you have to do is call the picturebox Invalidate() method, so it repaints the Image, and not draw anything in your Paint event handler.
Just use an additional Bitmap:
Bitmap original = LoadBitmap(...);
Bitmap copy = new Bitmap(original);
Graphics graph = Graphics.FromImage(copy);
// draw some extra
PictureBox1.Image = copy;

Winforms: SuspendLayout/ResumeLayout is not enough?

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.

Categories

Resources