I want to to make a shared drawing board in C#. This means that two people connected via a TCP connection can draw on this board. The idea (for now) is that people can click on the screen and draw. What do you think is the best method for this?
It's easy enough to draw a dot when the user clicks on a certain spot, but it gets a little more complicated when the user drags the mouse, where you need to draw a line between the last point and the current. Also that doesn't work so well, so I draw a dot where the line starts to improve things a bit, but it's not that good.
Lastly, I need to also send this over TCP, so I need to distinguish between the two. I hoped that I could just send points and have it draw it on the other side, but it seems it wouldn't work. Any ideas except also sending the type?
drawing http://img193.imageshack.us/img193/9697/drawingw.png
EDIT:
ok, I'm going with a IDrawingArgument interface that has Dispatch(myForm), and basically does double dispatch, so it solves the TCP problem (going to serialize/deserialize it).
Lines are still a bit bulky.
One little tip... on your mousemove event. keep a flag that wont fire the event again until the last event that set the flag turns it off. i.e.:
bool isDrawing = false;
public void myCanvas_MouseMove(object sender, EventArgs e)
{
if(!isDrawing)
{
isDrawing = true;
// Do drawing here
isDrawing = false;
}
}
This helped me a lot when doing drawing in a mousemove event.
Dots:
(x,y),(x2,y2),(x3,y3)
Lines:
(x,y,x2,y2),(x3,y3,x4,y4)
Thus, the format is a list of tuples. Tuples of size 4 are lines, of size 2 are points. Note that if your system gets more complicated, you'll really regret not just doing something like:
Dots:
D(x,y),D(x2,y2),D(x3,y3)
Lines:
L(x,y,x2,y2),L(x3,y3,x4,y4)
Related
I have WinForms application. I made an user control, which draws a map from coordinates of ca 10k lines. Actualy, not all lines are straight ones, but when the map is zoomed out fully - Bezier curves are irrelevant and are replaced with straight lines.
When the map is zoomed, I have smaller number of lines and curves, so the drawing is fast enough (below 15ms). But when it's zoomed out fully - I need to draw all lines (because all fit into viewport). This is painfully slow. On my very fast machine it takes about 1000ms, so on slower machines it would be an overkill.
Is there a simple way to speed up the drawing?
I use Graphics object for drawing and I set Graphics.Scale property to my map fit into my control.
Does this slow things down?
I use Graphics.TranslateTransform() to ensure the whole map is visible.
Both scale and translate is set only once in OnPaint() event handler.
Then there is a loop which draws ca 10k lines. And I just see them drawing on the screen.
Maybe WPF container would help?
Well, I could probably simplify the map to merge some lines, but I wonder if it's worth the effort. It would complicate the code greatly, would introduce much more calculations, use extra memory and I don't know if at the end of the day it would be considerably faster.
BTW, I tested that processing of all lines (converting from one structure to another with some aditional calculations) takes ca 10ms on my machine. So - the drawing alone costs 100x more time.
EDIT:
Now here's the new problem. I've turned double buffering on with:
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
Here's my messy OnPaint() handler:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (Splines == null) return;
var pens = new[] {
new Pen(TrackColor),
new Pen(TrackColor),
new Pen(RoadColor),
new Pen(RiverColor),
new Pen(CrossColor)
};
var b = Splines.Bounds;
Graphics g = e.Graphics;
g.PageScale = _CurrentScale;
g.TranslateTransform(-b.Left, -b.Top);
int i = 0;
foreach (var s in Splines) {
g.DrawLine(pens[s.T], s.A, s.D);
if (++i > 100) break;
//if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
//else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
}
foreach (var p in pens) p.Dispose();
}
Take my word the code works, if I only remove OptimizedDoubleBuffer from styles. When double buffering is on the handler executes properly, each DrawLine is executed with correct params. But the graphics is not displayed. CPU usage during resizing is next to zero. Like all DrawLine calls were ignored. What's happening here?
In a related post I've seen recently but can't find, the OP claimed to have seen a large speed-up when switching his control to use double-buffering. Apparently there's a substantial hit for drawing stuff to the screen.
Another thing you could try is decimating the point lists in the lines you draw when zoomed out. Instead of doing the decimation each frame, you could do it only once each time the zoom is changed.
Try double buffering as a possible solution or try to reduce the number of lines. Only testing will give you an answer for your application.
Winforms Double Buffering
Double buffering with Panel
The feasibility of this really depends on if you're using anti-aliasing, if the thing can rotate, if the thickness has to be very accurate, etc.
However you can always draw all the lines into a bitmap, then simply redraw the bitmap unless the map data itself has actually changed. Of course then you get into having different bitmaps for different zoom levels, hiding and showing them, multiple bitmaps in a grid for the high details etc.
It's definitely not ideal, but if you really do need to draw thousands of lines on a 20ms refresh though.. it might be your only real option.
Or you could use lower level of drawing, outside GDI+. one such example is SlimDX. This wrapper allows you to create a directX device write from your windows controls and forms. Once DirectX is in action, the speed can increase up to several times.
2ndly, when drawing on win panel even with DoubleBuffered enabled, you always have to Invalidate the panel which asks the Environment to call the OnPaint event which actual draws using the system provided Graphics object. This invalidation usually requires a timer with fire rate more than 30 to five you a feeling of smooth playback. Now, when the load increases, the subsequent timer event is delayed since everything is happening under a single thread. And the timer must Yield the thread for around 25ms after every fire (windows OS limitation). Cross Thread access ia not allowed, using which a System.Threading.Timer could have prevent this jitter.
See this link for an example where I have tried to transfer my existing GDI code to DirectX. The code uses a lot of graphics attributes which i have incorporated in the wrapper which can draw on both GDI and DirectX.
https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk
I've attached some MouseMove and MouseClick events to my program and next up is one of these:
Get "global" mouse movement, so that I can read the mouse location even outside the form.
Prevent my mouse from leaving the form in the first place.
My project is a game so it'd be awesome to prevent the mouse leaving my form like most other games do (ofc. you can move it out if you switch focus with alt+tab fe.) and taking a look at answers to other questions asking for global mosue movement, they seem pretty messy for my needs.
Is there an easy way to prevent my mouse from going outside my form's borders? Or actually to prevent it from going OVER the borders in the first place, I want the mouse to stay inside the client area.
Additional info about the game:
The game is a short, 5-30 seconds long survival game (it gets too hard after 30 seconds for you to stay alive) where you have to dodge bullets with your mouse. It's really annoying when you move your mouse out of the form and then the player (System.Windows.Forms.Panel attached to mouse) stops moving and instantly gets hit by a bullet. This is why preventing mouse from leaving the area would be good.
Late answer but might come in handy. You could subscribe the form to MouseLeave and MouseMove events and handle them like this :
private int X = 0;
private int Y = 0;
private void Form1_MouseLeave(object sender, EventArgs e)
{
Cursor.Position = new Point(X, Y);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (Cursor.Position.X < this.Bounds.X + 50 )
X = Cursor.Position.X + 20;
else
X = Cursor.Position.X - 20;
if (Cursor.Position.Y < this.Bounds.Y + 50)
Y = Cursor.Position.Y + 20;
else
Y = Cursor.Position.Y - 20;
}
The above will make sure the mouse cursor never leaves the bounds of the form. Make sure you unsubscribe the events when the game is finished.
Edit :
Hans Passants's answer makes more sense than my answer. Use Cursor.Clip on MouseEnter :
private void Form1_MouseEnter(object sender, EventArgs e)
{
Cursor.Clip = this.Bounds;
}
You could free the cursor in case of any error/crash (I'm sure you could catch'em) :
Cursor.Clip = Rectangle.Empty;
You cannot trap the mouse, that would prevent the user from, say, operating the Start menu. Closest you can get is assigning the Cursor.Clip property. But it is easily defeated by the user pressing Ctrl+Esc for example, there is no notification for this.
Best thing to do is to subscribe the form's Deactivated event, it reliably tells you that the user switched to another program. The Activated event tells you when the user moved back. Of course the user will have few reasons to actually do this when the game score depends on keeping a game object moving. So don't forget to give the user an easy way to pause the game with, say, the Escape key.
I don't know a solution for your exact problem, but I have a completely different idea for you. I don't know how your game works, but based on what you told me, why not make it a step harder: Add borders to the game-area, for example 4 pixels wide rectangles, which you are not allowed to touch. If you touch them, you die and the mouse gets released.
You can use the Cursor class. For example:
int X = Cursor.Position.X;
int Y = Cursor.Position.Y;
As for preventing the user to move the mouse outside the form, the best approach would probably be if you had someway to know what is the coordinates of your form on the screen and attach a MouseMove event, and check if the mouse is inside the form rectangle.
To know the form position on the screen take a look at this question.
I wouldn't recommend the global mouse movement control for two reasons.
It's bad design, you should respect the bounds of the operating system. Make the application full screen if you want this kind of behaviour. The only applications that should perform these kind of operations are "kiosk" mode applications which lock down the entire OS (to prevent operator abuse).
Global key hooks are messy, aren't guaranteed to work and are dangerous because they affect a key part of the operating system (all controls). A bug in your code could result in requiring a reboot on the machine.
That said, last time I checked (a while ago, on Vista) SetWindowsHookEx still works (but its not officially supported IIRC), it's an unmanaged call so you'll have to pinvoke but with it you can refuse to pass on messages that would move the mouse outside of the bounds of your application. I'm not 100% sure if the OS will let you beat it to the cursor control (I've only blocked keyboards before on desktop boxes) but its probably your best shot.
My program is basically about analyzing videos.
A major part is to plot a diagram showing (f.e.) brightness per frame on y-axis and every frame number on x-axis. Because the program is written in C# and uses WPF, D³ was the way to go for plotting.
Now the user might see a peak signal in the diagram and wants to look on that single frame to understand why it's so bright (it might be just natural, or an encoding-artifact).
There comes my question: The most intuitive way for the user to click on the diagram where the peak is, which jumps the video preview (other GUI element) right to that frame. So I need the x-coordinate (=frame number) of the user click on the diagram.
It is possible to manually analyze the mouse-input event, but that would take much work (because the x-axis is different for each video and the entire diagram can be resized, so absolute coordinates are a no go).
But maybe something similar is already implemented by D³. I searched the documentary, but didn't find anything useful. The only piece of information was using a "DraggablePoint", but that's where the trail goes cold.
Does someone of you know how to get the x-coordinate without much work?
It sure is possible! The way that I have done it in the past is to add a CursorCoordinateGraph object to my plotters children, and it automatically tracks the mouse position on the graph in relation to the data. You can turn off the visual features of the CursorCoordinateGraph and use it for tracking only. Here's what it would look like:
CursorCoordinateGraph mouseTrack;
plotter.Children.Add(mouseTrack);
mouseTrack.ShowHorizontalLine = false;
mouseTrack.ShowVerticalLine = false;
And your mouse click event would look like this:
private void plotter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point mousePos = mouseTrack.Position;
var transform = plotter.Viewport.Transform;
Point mousePosInData = mousePos.ScreenToData(transform);
double xValue = mousePosInData.X;
}
You can then use xValue and manipulate it however you would like to achieve your desired effect.
I have no clue how to ask this question but this is a last ditch effort as I am stumped.
I have this bit of c# code that will send off to a server (using JSON) a request to draw a wms layer to a map. That server goes and does its duty and bam the wms layer appears on the map.
IN this bit of code I sometimes wanna output the times they are clicking on to render this map (its a timeline based render), so using a UserControl in c# that contains a picture box, I render this timeline and then put up a tooltip that shows the various times as the mouse moves along this pictureBox.
The Tooltip is defined as globally:
ToolTip myToolTip;
Then when I use it in my bit of code:
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
//...blah blah blah stuff to calculate time where user clicked based on width of timeline drawn in the picturebox etc.
prisms.callServer("Timetable", "setSelection", selection, null); //call to server to tell it to render the map at the time defined by selection
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
//...blah blah..code to calculate mTheDate showing what the time is where they are where they are hovering
myToolTip.Show(mTheDate, this, toolTipX, toolTipY, 100);
}
So when the scenario above happens, the map doesn't render until i move the mouse off the userControl that has the picturebox on it.
BUT if i take out the myToolTip.show in the MouseMove then minute I click the map renders (i realize it could have lots to do with the WMS server I am hitting etc etc, but it leads me to believe it is something with the tooltips, since removing it makes things work well enough that the map renders without having to move my mouse off the picturebox control).
So I realize this is a vague, hazy question but I am stuck and just throwing this out there for any ideas. Maybe there is something more to the tooltips I don't understand or I need a different approach that does not use the tool tips?
I should also state that the prisms.Call server does get executed (using breakpoint figured this out) even with tool tip there so not sure what is going on.
You could always hide the tooltip (if its showing) while you run the WMS call, then resume showing the tooltip.
I do not have any tooltip-specific knowledge.
I am trying to make it so that the user can scroll a richtextbox by clicking the window the richtexbox is on and dragging the mouse. Unfortunately I haven't gotten very far:
private void Main_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
}
}
I've seen some suggestions on the web to track the last several x,y coordinates of the mouse and compare them to the x,y coordinates each time the mouse move event is triggered. Is there any less messy way to do this?
None that I know of. Unless you're using an API that handles it for you, you have to keep track of the information manually. And even if you did use an API just for mouse drags, it'd do the storing itself and likely just pass back the current X and Y, and the difference in X and Y, since the API wouldn't know what you want done with the information.
You'd be handling a little bit less information, but saving only about 5 lines or so of code to get the same result.